Related
Is there a way to incorporate Perlin Noise into the Ursina game engine in Python to give it a Minecraft feeling? I think I may be onto something, but for some reason, the y value is not varying. Do you mind helping me out?
The code that I have so far:
from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
from perlin_noise import PerlinNoise;
app = Ursina()
noise = PerlinNoise(octaves=10, seed=1)
# Define a Voxel class.
# By setting the parent to scene and the model to 'cube' it becomes a 3d button.
class Voxel(Button):
def __init__(self, position=(0,0,0)):
super().__init__(
parent = scene,
position = position,
model = 'cube',
origin_y = .5,
texture = 'white_cube',
color = color.color(0, 0, random.uniform(.9, 1.0)),
highlight_color = color.lime,
)
def input(self, key):
if self.hovered:
if key == 'left mouse down':
voxel = Voxel(position=self.position + mouse.normal)
if key == 'right mouse down':
destroy(self)
for z in range(8):
for x in range(8):
y = .25 + noise([x, z])
voxel = Voxel(position=(x, y,z))
player = FirstPersonController()
app.run()
As shown in the usage examples, you'll have to scale the noise value with an additional division:
for z in range(8):
for x in range(8):
y = .25 + noise([x/8, z/8])
voxel = Voxel(position=(x, y,z))
You'll probably want to replace that magic number with a constant.
You can use this:
seed=random.randint(1,1000000)
noise = PerlinNoise(octaves=3,seed=seed)
for z in range(8):
for x in range(8):
y = noise([x * 0.02,z * 0.02])
y = math.floor(y * 7.5)
voxel = Voxel(position=(x,y,z))
First, you did not say "Perlin Noise" correctly, so remove the semi-colon, then you'll use this code:
noise = PerlinNoise(octaves=intgeer,seed=0)
for z in range(8):
for x in range(8):
y = 0. 25 noise([x * 0.02,z * 0.02])
voxel = Voxel(position=(x,y,z))
It sounds like Voxel(...) places a voxel at the specified position, right? If so, then you would need to loop over the whole column to place the blocks into.
And if your noise library doesn't handle frequency and amplitude scaling internally, then you need to do that externally. Multiply the input coordinates by fractions, and multiply the output value by larger values.
min_height = 4.0
max_height = 16.0
for z in range(8):
for x in range(8):
height = # some noise formula
# if noise() range is 0 to 1: min_height + noise([x * 0.05, z * 0.05]) * (max_height - min_height)
# if noise() range is -1 to 1: min_height + (noise([x * 0.05, z * 0.05]) * 0.5 + 0.5) * (max_height - min_height)
for y in range(height): # might need to convert height to an integer
voxel = Voxel(position=(x,y,z))
Also please keep in mind the problems of Perlin for noise in its unmitigated form. The algorithm's popularity by name far outweighs its canonical form's true merit in light of its square alignment issues and the broader space of options we have now, and in spite of the overwhelming amount of content that still teaches it in a vacuum. See the final paragraph from my last answer for more info: https://stackoverflow.com/a/73348943
I would suggest this: https://pypi.org/project/pyfastnoiselite/
from pyfastnoiselite.pyfastnoiselite import (
FastNoiseLite, NoiseType, FractalType,
)
min_height = 4.0
max_height = 16.0
noise = FastNoiseLite(0)
noise.noise_type = NoiseType.NoiseType_OpenSimplex2
noise.fractal_type = FractalType.FractalType_FBm
noise.fractal_octaves = 4
noise.frequency = 0.03
for z in range(8):
for x in range(8):
height = min_height + (noise.get_noise(x, z) * 0.5 + 0.5) * (max_height - min_height)
for y in range(height): # might need to convert height to an integer
Voxel(position=(x,y,z))
or for 3D noise to enable overhangs, you vary that heightmap within Y as well:
from pyfastnoiselite.pyfastnoiselite import (
FastNoiseLite, NoiseType, FractalType,
)
min_height = 4.0
max_height = 16.0
noise = FastNoiseLite(0)
noise.noise_type = NoiseType.NoiseType_OpenSimplex2
noise.fractal_type = FractalType.FractalType_FBm
noise.fractal_octaves = 4
noise.frequency = 0.03
for z in range(8):
for x in range(8):
for y in range(min_height): # might need to convert bounds to integers
Voxel(position=(x,y,z))
for y in range(min_height, max_height): # might need to convert bounds to integers
effective_height = min_height + (noise.get_noise(x, y, z) * 0.5 + 0.5) * (max_height - min_height)
if effective_height > y:
Voxel(position=(x,y,z))
Examples untested. There may be some changes to parameters or even syntax to get these working! Feel free to make the needed edits to this answer to get it working properly.
Also min_height + (noise.get_noise(x, y, z) * 0.5 + 0.5) * (max_height - min_height) can be simplified down to noise.get_noise(x, y, z) * factor + offset, where
factor = 0.5 * (max_height - min_height)
offset = factor + min_height
I am attempting to create a grid of locators that serve as projected points onto a parallel finite plane from a camera in maya at a specified depth. The grid should line up with a specified resolution so as to match rendered output.
At the moment my calculations are off and I am looking for some help to ascertain how my formula for ascertaining the projected points is incorrect.
I have a self contained python script and image showing the current position of locators that are spawned as an example.
image showing current spawned locators are off on y and z axis
import maya.cmds as mc
import maya.OpenMaya as om
res = [mc.getAttr('defaultResolution.width'),
mc.getAttr('defaultResolution.height')]
print res
grid = [5, 5]
def projectedGridPoint(camera, coord, depth, res):
selList = om.MSelectionList()
selList.add(camera)
dagPath = om.MDagPath()
selList.getDagPath(0,dagPath)
dagPath.extendToShape()
camMtx = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
mFloatMtx = fnCam.projectionMatrix()
projMtx = om.MMatrix(mFloatMtx.matrix)
#center of camera
eyePt = fnCam.eyePoint()
#offset position
z = eyePt.z - depth
#calculated xy positions
x = (2 * z * coord[0] / res[0]) - z
y = (2 * z * coord[1] / res[1]) - z
return om.MPoint(x,y,depth) * camMtx * projMtx.inverse()
for y in range(grid[1] + 1):
for x in range(grid[0] + 1):
coord = ( x / float(grid[0]) * res[0], y / float(grid[1]) * res[1] )
pt = projectedGridPoint('camera1', coord, 10, res)
mc.spaceLocator(a=1, p=[pt.x, pt.y, pt.z])
Once I adjusted Theodox's answer to account for all possible grid divisions, such that the ndc_x and ndc_y was always in the range of -1 and 1. I was able to get a working solution.
import maya.api.OpenMaya as om
import maya.cmds as cmds
def projectedGridPoint(camera, coord, depth):
selList = om.MGlobal.getSelectionListByName(camera)
dagPath = selList.getDagPath(0)
dagPath.extendToShape()
view = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
projection = om.MMatrix(fnCam.projectionMatrix())
viewProj = projection * view
r = om.MPoint(coord[0],coord[1], -1 * depth) * projection.inverse()
return r.homogenize() * view
xx, yy = (6, 6)
for y in range(yy + 1):
for x in range(xx + 1):
ndc_x = -1
ndc_y = -1
if x > 0:
ndc_x = (x / float(xx) * 2) - 1
if y > 0:
ndc_y = (y / float(yy) * 2) - 1
coord = ( ndc_x, ndc_y)
print coord
pt = projectedGridPoint('camera1', coord, 0)
c,_ = cmds.polyCube(w = 0.1, d = 0.1, h = 0.1)
cmds.xform(c, t = (pt[0], pt[1], pt[2]))
I think you want something a bit more like this (note, i converted it to API 2 to cut down on the boilerplate)
import maya.api.OpenMaya as om
import maya.cmds as cmds
def projectedGridPoint(camera, coord, depth):
selList = om.MGlobal.getSelectionListByName(camera)
dagPath = selList.getDagPath(0)
dagPath.extendToShape()
view = dagPath.inclusiveMatrix()
fnCam = om.MFnCamera(dagPath)
projection = om.MMatrix(fnCam.projectionMatrix())
viewProj = projection * view
r = om.MPoint(coord[0],coord[1], -1 * depth) * projection.inverse()
return r.homogenize() * view
xx, yy = (2, 2)
for y in range(yy):
for x in range(xx):
ndc_x = 2.0 * x / float(xx - 1) - 1
ndc_y = 2.0 * y / float(yy - 1) - 1
coord = ( ndc_x, ndc_y)
pt = projectedGridPoint('camera1', coord,0)
c,_ = cmds.polyCube(w = 0.1, d = 0.1, h = 0.1)
cmds.xform(c, t = (pt[0], pt[1], pt[2]))
The coords are supplied as normalized device coordinates (from -1,-1 to 1, 1 at the corners of the view) and the depth goes from the near to far clip planes -- a depth of 1 is right on the near plane and a depth of 0 is on the far plane. I think in practice I'd lock the depth at 0 and use the clip plane setting on the camera to set the depth
edit I rationalized the original, wonky method of converting index values to NDC coordinates
I'm currently drawing the Mandelbrot set pixel by pixel with PhotoImage and tkinter. I'm using basically the algorithm directly with no modifications. Are there methods to make the calculation faster? Maybe filling in large areas of color quickly, or precalcuating constants?
Part of the code:
ITERATIONS = 50
WIDTH, HEIGHT = 600, 600
CENTER = (-.5, 0)
DIAMETER = 2.5
def mandel(c):
z = 0
for i in range(ITERATIONS):
z = z**2 + c
if abs(z) > 2:
return i
return ITERATIONS
root = Tk()
canvas = Canvas(root, width=WIDTH,height=HEIGHT)
canvas.pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
canvas.create_image((WIDTH/2, HEIGHT/2), image=img, state="normal")
real = CENTER[0] - 0.5 * DIAMETER
imag = CENTER[1] - 0.5 * DIAMETER
def color(i):
colors = ("#0000AA", "#88DDFF", "#FF8800", "#000000")
if i == ITERATIONS:
return colors[-1]
else:
choice = (i//2) % len(colors)
return colors[choice]
for x in range(WIDTH):
for y in range(HEIGHT):
i = mandel(complex(real, imag))
img.put(color(i), (x, HEIGHT-y))
imag += DIAMETER / HEIGHT
imag = CENTER[1] - 0.5 * DIAMETER
real += DIAMETER / WIDTH
mainloop()
Setting one pixel at a time is likely the main source of the slowdown. Instead of calling put for each pixel, computer a whole row of pixels, or an entire matrix of pixels, and then call put one time at the end of the loop.
You can find an example here, among other places: https://web.archive.org/web/20170512214049/http://tkinter.unpythonic.net:80/wiki/PhotoImage#Fill_Many_Pixels_at_Once
Here is my code, it draws a 640x480 Mandelbrot in 8-9 seconds.
It does up to 256 iterations per pixel, uses a color map list, 'puts' only once to PhotoImage and doesn't rely on symetry, so it could show any zoomed area of the set.
It's a pity that Tkinter doesn't allow access to the raster information of PhotoImage as a buffer and that the clumsy string is required.
from tkinter import Tk, Canvas, PhotoImage,NW,mainloop
from time import clock
def mandel(kx,ky):
""" calculates the pixel color of the point of mandelbrot plane
passed in the arguments """
global clr
maxIt = 256
c = complex(kx, ky)
z = complex(0.0, 0.0)
for i in range(maxIt):
z = z * z + c
if abs(z) >= 2.0:
return (255-clr[i],0,0)
return(0,0,0)
def prepare_mdb(xa,xb,ya,yb):
""" pre-calculates coordinates of the mandelbrot plane required for each
pixel in the screen"""
global x,y,xm,ym
xm.clear
ym.clear
xm=[xa + (xb - xa) * kx /x for kx in range(x)]
ym=[ya + (yb - ya) * ky /y for ky in range(y)]
x=640
y=480
#corners of the mandelbrot plan to display
xa = -2.0; xb = 1.0
ya = -1.5; yb = 1.5
#precalculated color table
clr=[ int(255*((i/255)**12)) for i in range(255,-1,-1)]
xm=[]
ym=[]
prepare_mdb(xa,xb,ya,yb)
#Tk
window = Tk()
canvas = Canvas(window, width = x, height = y, bg = "#000000")
t1=clock()
img = PhotoImage(width = x, height = y)
canvas.create_image((0, 0), image = img, state = "normal", anchor = NW)
pixels=" ".join(("{"+" ".join(('#%02x%02x%02x' % mandel(i,j) for i in xm))+"}" for j in ym))
img.put(pixels)
canvas.pack()
print(clock()-t1)
mainloop()
Pure python is not that fast for numeric code. The easiest way to speed things up would be to use PyPy. If that is not fast enough, vectorize your algorithms using numpy. If that is still not fast enough, use Cython, or consider rewriting it in C.
For a modest increase in speed (but not enough to offset the difference between a compiled language and an interpreted one), you can precalculate some of the values.
Right now, you're calculating DIAMETER / HEIGHT once per inner loop, and CENTER[1] - 0.5 * DIAMETER as well as DIAMETER / WIDTH once per outer loop. Do this beforehand.
len(colors) also won't change and can be replaced by a constant. In fact, I'd probably write that function as
def color(i):
if i == ITERATIONS:
return "#000000"
else:
return ("#0000AA", "#88DDFF", "#FF8800", "#000000")[(i//2) % 4]
# are you sure you don't want ("#0000AA", "#88DDFF", "#FF8800")[(i//2) % 3] ?
Also, x**2 is slower than x*x (because the x**y operator doesn't shortcut for the trivial case of y==2), so you can speed that calculation up a bit.
Most time is spent in the inner loop in mandel(). z*z instead of z**2 had a slight effect. There is not much else to speed up there that I can see. Removing constants from other loops had little effect, though I tend to prefer doing so. Choosing ITERATIONS so that ITERATIONS//2 % len(colors) == len(colors)-1, as in 46 //2 % 4 == 3, allows simplification of the code. Exploiting symmetry around the x-axis cuts time in half. Starting imag at 0 avoids the roundoff error of 300 subtractions from +/- DIAMETER / 2 and results in a clean center line in the image.
from tkinter import *
ITERATIONS = 46
WIDTH, HEIGHT = 601, 601 # odd for centering and exploiting symmetry
DIAMETER = 2.5
start = (-.5 - DIAMETER / 2, 0) # Start y on centerline
d_over_h = DIAMETER / HEIGHT
d_over_w = DIAMETER / WIDTH
def mandel(c):
z = 0
for i in range(ITERATIONS):
z = z*z + c
if abs(z) > 2:
return i
return ITERATIONS
root = Tk()
canvas = Canvas(root, width=WIDTH,height=HEIGHT)
canvas.pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
canvas.create_image(((WIDTH+1)//2, (HEIGHT+1)//2), image=img, state="normal")
real, imag = start
colors = ("#0000AA", "#88DDFF", "#FF8800", "#000000")
ncolors = len(colors)
yrange = range(HEIGHT//2, -1, -1) # up from centerline
ymax = HEIGHT - 1
for x in range(WIDTH):
for y in yrange:
i = mandel(complex(real, imag))
color = colors[i//2 % ncolors]
img.put(color, (x, y))
img.put(color, (x, ymax - y))
imag += d_over_h
imag = start[1]
real += d_over_w
mainloop()
Complex numbers in python can be slow, especially if you call abs(x) every iteration. Representing the complex number with c_r and c_i, for real and imaginary parts, reduces the number of calculations you do every iteration.
def mandel(c):
z = 0
for i in range(ITERATIONS):
z = z**2 + c
if abs(z) > 2:
return i
return ITERATIONS
instead of z = 0, replace it with z_r,z_i=0,0
we also have to change c in the parameters.
Now we have:
def mandel(c_r,c_i):
z_r = 0
z_i = 0
for i in range(ITERATIONS):
z = z**2 + c
if abs(z) > 2:
return i
return ITERATIONS
Instead of using abs(z) > 2, we can now use z_r * z_r + z_i + z_i > 4
Also, we replace z**2 + c with a new version using our new variables
(Know that (a+bi)^2 = a^2 - b^2 + 2abi
def mandel(c_r,c_i):
z_r = 0
z_i = 0
z_r_squared = 0
z_i_squared = 0
for i in range(ITERATIONS):
z_r_squared = z_r * z_r
z_i_squared = z_i * z_i
z_r = z_r_squared - z_i_squared + c_r
z_i = 2 * z_r * z_i + c_i
if z_r_squared + z_r_squared > 4:
return i
return ITERATIONS
Finally, you have to change where the mandelbrot function is called, so
i = mandel(complex(real, imag))
becomes
i = mandel(real, imag)
Is there a way to draw direction fields in python?
My attempt is to modify http://www.compdigitec.com/labs/files/slopefields.py giving
#!/usr/bin/python
import math
from subprocess import CalledProcessError, call, check_call
def dy_dx(x, y):
try:
# declare your dy/dx here:
return x**2-x-2
except ZeroDivisionError:
return 1000.0
# Adjust window parameters
XMIN = -5.0
XMAX = 5.0
YMIN = -10.0
YMAX = 10.0
XSCL = 0.5
YSCL = 0.5
DISTANCE = 0.1
def main():
fileobj = open("data.txt", "w")
for x1 in xrange(int(XMIN / XSCL), int(XMAX / XSCL)):
for y1 in xrange(int(YMIN / YSCL), int(YMAX / YSCL)):
x= float(x1 * XSCL)
y= float(y1 * YSCL)
slope = dy_dx(x,y)
dx = math.sqrt( DISTANCE/( 1+math.pow(slope,2) ) )
dy = slope*dx
fileobj.write(str(x) + " " + str(y) + " " + str(dx) + " " + str(dy) + "\n")
fileobj.close()
try:
check_call(["gnuplot","-e","set terminal png size 800,600 enhanced font \"Arial,12\"; set xrange [" + str(XMIN) + ":" + str(XMAX) + "]; set yrange [" + str(YMIN) + ":" + str(YMAX) + "]; set output 'output.png'; plot 'data.txt' using 1:2:3:4 with vectors"])
except (CalledProcessError, OSError):
print "Error: gnuplot not found on system!"
exit(1)
print "Saved image to output.png"
call(["xdg-open","output.png"])
return 0
if __name__ == '__main__':
main()
However the best image I get from this is.
How can I get an output that looks more like the first image? Also, how can I add the three solid lines?
You can use this matplotlib code as a base. Modify it for your needs.
I have updated the code to show same length arrows. The important option is to set the angles option of the quiver function, so that the arrows are correctly printed from (x,y) to (x+u,y+v) (instead of the default, which just takes into account of (u,v) when computing the angles).
It is also possible to change the axis form "boxes" to "arrows". Let me know if you need that change and I could add it.
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
fig = plt.figure()
def vf(x, t):
dx = np.zeros(2)
dx[0] = 1.0
dx[1] = x[0] ** 2 - x[0] - 2.0
return dx
# Solution curves
t0 = 0.0
tEnd = 10.0
# Vector field
X, Y = np.meshgrid(np.linspace(-5, 5, 20), np.linspace(-10, 10, 20))
U = 1.0
V = X ** 2 - X - 2
# Normalize arrows
N = np.sqrt(U ** 2 + V ** 2)
U = U / N
V = V / N
plt.quiver(X, Y, U, V, angles="xy")
t = np.linspace(t0, tEnd, 100)
for y0 in np.linspace(-5.0, 0.0, 10):
y_initial = [y0, -10.0]
y = odeint(vf, y_initial, t)
plt.plot(y[:, 0], y[:, 1], "-")
plt.xlim([-5, 5])
plt.ylim([-10, 10])
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
I had a lot of fun making one of these as a hobby project using pygame. I plotted the slope at each pixel, using shades of blue for positive and shades of red for negative. Black is for undefined. This is dy/dx = log(sin(x/y)+cos(y/x)):
You can zoom in & out - here is zoomed in on the middle upper part here:
and also click on a point to graph the line going through that point:
It's just 440 lines of code, so here is the .zip of all the files. I guess I'll excerpt relevant bits here.
The equation itself is input as a valid Python expression in a string, e.g. "log(sin(x/y)+cos(y/x))". This is then compiled. This function here graphs the color field, where self.func.eval() gives the dy/dx at the given point. The code is a bit complicated here because I made it render in stages - first 32x32 blocks, then 16x16, etc. - to make it snappier for the user.
def graphcolorfield(self, sqsizes=[32,16,8,4,2,1]):
su = ScreenUpdater(50)
lastskip = self.xscreensize
quitit = False
for squaresize in sqsizes:
xsquaresize = squaresize
ysquaresize = squaresize
if squaresize == 1:
self.screen.lock()
y = 0
while y <= self.yscreensize:
x = 0
skiprow = y%lastskip == 0
while x <= self.xscreensize:
if skiprow and x%lastskip==0:
x += squaresize
continue
color = (255,255,255)
try:
m = self.func.eval(*self.ct.untranscoord(x, y))
if m >= 0:
if m < 1:
c = 255 * m
color = (0, 0, c)
else:
#c = 255 - 255 * (1.0/m)
#color = (c, c, 255)
c = 255 - 255 * (1.0/m)
color = (c/2.0, c/2.0, 255)
else:
pm = -m
if pm < 1:
c = 255 * pm
color = (c, 0, 0)
else:
c = 255 - 255 * (1.0/pm)
color = (255, c/2.0, c/2.0)
except:
color = (0, 0, 0)
if squaresize > 1:
self.screen.fill(color, (x, y, squaresize, squaresize))
else:
self.screen.set_at((x, y), color)
if su.update():
quitit = True
break
x += xsquaresize
if quitit:
break
y += ysquaresize
if squaresize == 1:
self.screen.unlock()
lastskip = squaresize
if quitit:
break
This is the code which graphs a line through a point:
def _grapheqhelp(self, sx, sy, stepsize, numsteps, color):
x = sx
y = sy
i = 0
pygame.draw.line(self.screen, color, (x, y), (x, y), 2)
while i < numsteps:
lastx = x
lasty = y
try:
m = self.func.eval(x, y)
except:
return
x += stepsize
y = y + m * stepsize
screenx1, screeny1 = self.ct.transcoord(lastx, lasty)
screenx2, screeny2 = self.ct.transcoord(x, y)
#print "(%f, %f)-(%f, %f)" % (screenx1, screeny1, screenx2, screeny2)
try:
pygame.draw.line(self.screen, color,
(screenx1, screeny1),
(screenx2, screeny2), 2)
except:
return
i += 1
stx, sty = self.ct.transcoord(sx, sy)
pygame.draw.circle(self.screen, color, (int(stx), int(sty)), 3, 0)
And it runs backwards & forwards starting from that point:
def graphequation(self, sx, sy, stepsize=.01, color=(255, 255, 127)):
"""Graph the differential equation, given the starting point sx and sy, for length
length using stepsize stepsize."""
numstepsf = (self.xrange[1] - sx) / stepsize
numstepsb = (sx - self.xrange[0]) / stepsize
self._grapheqhelp(sx, sy, stepsize, numstepsf, color)
self._grapheqhelp(sx, sy, -stepsize, numstepsb, color)
I never got around to drawing actual lines because the pixel approach looked too cool.
Try changing your values for the parameters to this:
XSCL = .2
YSCL = .2
These parameters determine how many points are sampled on the axes.
As per your comment, you'll need to also plot the functions for which the derivation dy_dx(x, y) applies.
Currently, you're only calculating and plotting the slope lines as calculated by your function dy_dx(x,y). You'll need to find (in this case 3) functions to plot in addition to the slope.
Start by defining a function:
def f1_x(x):
return x**3-x**2-2x;
and then, in your loop, you'll have to also write the desired values for the functions into the fileobj file.
My goal is to double the picture size then change the left half to grayscale, then change the green value of the top right half and the blue value of the bottom right half. I have values that I found in my textbook for the grayscale but im not sure if thats what I actually use. And I also am unsure if I program each of these different values using for loops or just something different
So far my code is:
def crazyPic(newGreen,newBlue,pic,file):
show(pic)
newPic = makeEmptyPicture(getWidth(pic)*2,getHeight((pic)*2
for x in range(width):
for y in range(height):
for px in getPixel(pic,0,100):
nRed = getRed(px) * 0.299
nGreen = getGreen(px) * 0.587
nBlue = getBlue(px) * 0.114
luminance = nRed + nGreen + nBlue
setColor(px,makeColor(luminance,luminance,luminance)
I shouldn't give full answers as JES is an application designed for students, but I think that three months later one can give a full working sample which can be used as reference for the others...
This should be close to what you attempted to do:
Note : your approach of a simple double loop over x and y was the right one.
def crazyPic(pic, newRed, newGreen, newBlue):
w = getWidth(pic)
h = getHeight(pic)
new_w = w * 2
new_h = h * 2
newPic = makeEmptyPicture(w * 2, h * 2)
for x in range(new_w):
for y in range(new_h):
new_px = getPixel(newPic, x, y)
# Top-left: B&W
if (x < w) and (y < h):
px = getPixel(pic, x, y)
nRed = getRed(px) * newRed #0.299
nGreen = getGreen(px) * newGreen #0.587
nBlue = getBlue(px) * newBlue #0.114
luminance = nRed + nGreen + nBlue
new_col = makeColor(luminance, luminance, luminance)
# Top-right
elif (y < h):
px = getPixel(pic, x - w, y)
nRed = getRed(px) * newRed
new_col = makeColor(nRed, getGreen(px), getBlue(px))
# Bottom-left
elif (x < w):
px = getPixel(pic, x, y - h)
nGreen = getGreen(px) * newGreen
new_col = makeColor(getGreen(px), nGreen, getBlue(px))
# Bottom-right
else:
px = getPixel(pic, x - w, y - h)
nBlue = getBlue(px) * newBlue
new_col = makeColor(getGreen(px), getBlue(px), nBlue)
setColor(new_px, new_col)
return newPic
file = pickAFile()
picture = makePicture(file)
#picture = crazyPic(picture, 0.299, 0.587, 0.114)
# Here, with my favorite r, g, b weights
picture = crazyPic(picture, 0.21, 0.71, 0.07)
writePictureTo(picture, "/home/quartered.jpg")
show(picture)
Output (Painting by Antoni Tapies):
......From......
Here is a more detailed thread about greyscale.