Zoom image to pixel level
For an art project, one of the things I'll be doing is zooming in on an image to a particular pixel. I've been rubbing my chin and would love some advice on how to proceed.
Here are the input parameters:
Screen:
sw - screen width
sh - screen height
Image:
iw - image width
ih - image height
Pixel:
px - x position of pixel in imag开发者_运维百科e
py - y position of pixel in image
Zoom:
zf - zoom factor (0.0 to 1.0)
Background colour:
bc - background colour to use when screen and image aspect ratios are different
Outputs:
The zoomed image (no anti-aliasing)
The screen position/dimensions of the pixel we are zooming to.
When zf is 0 the image must fit the screen with correct aspect ratio.
When zf is 1 the selected pixel fits the screen with correct aspect ratio.
One idea I had was to use something like povray and move the camera towards a big image texture or some library (e.g. pygame) to do the zooming. Anyone think of something more clever with simple pseudo code?
To keep it more simple you can make the image and screen have the same aspect ratio. I can live with that.
I'll update with more info as its required.
UPDATE
Converted accepted answer to PHP
Image Pixel Zoom on GitHub
If color values of original image are given as array
image[x][y]
Then color values of zoomed image are
image[x+zf*(px-x)][y+zf*(py-y)]
Regarding the windows size/image size - initial preparation of image should take care of that: zoom the image up to the point that it would not fit the window any more and fill the remaining pixels with your preferred background colour.
In python you can do something like
def naivezoom(im, px, py, zf, bg):
out = Image.new(im.mode, im.size)
pix = out.load()
iw, ih = im.size
for x in range(iw):
for y in range(ih):
xorg = x + zf*(px - x)
yorg = y + zf*(py - y)
if xorg >= 0 and xorg < iw and yorg >= 0 and yorg < ih:
pix[x,y] = im.getpixel( (xorg , yorg) )
else:
pix[x,y] = bg
return out
after you set
im = Image.open("filename.ext")
with objects from
import Image
EDIT: Given stackoverflow logo you will get
for zf = 0.3, around point 25,6
for zf = 0.96, around the same point
Images were obtained with following code
#!/bin/env python
from Tkinter import *
import Image
import ImageTk
def naivezoom(im, p, zf, bg):
out = Image.new(im.mode, im.size)
pix = out.load()
iw, ih = im.size
for x in range(iw):
for y in range(ih):
xorg = x + zf*(p[0] - x)
yorg = y + zf*(p[1] - y)
if xorg >= 0 and xorg < iw and yorg >= 0 and yorg < ih:
pix[x,y] = im.getpixel( (xorg , yorg) )
else:
pix[x,y] = bg
return out
class NaiveTkZoom:
def __init__(self, parent=None):
root = Tk()
self.im = Image.open('logo.jpg')
self.zf = 0.0
self.deltazf = 0.02
self.p = ( 0.1*self.im.size[0],0.1*self.im.size[1])
self.bg = 255
canvas = Canvas(root, width=self.im.size[0]+20 , height=self.im.size[1]+20)
canvas.pack()
root.bind('<Key>', self.onKey)
self.canvas = canvas
self.photo = ImageTk.PhotoImage(self.im)
self.item = self.canvas.create_image(10, 10, anchor=NW, image=self.photo)
def onKey(self, event):
if event.char == "+":
if self.zf < 1:
self.zf += self.deltazf
elif event.char == "-":
if self.zf > 0:
self.zf -= self.deltazf
self.out = naivezoom(self.im, self.p, self.zf, self.bg)
self.photo = ImageTk.PhotoImage(self.out)
self.canvas.delete(self.item)
self.item = self.canvas.create_image(10, 10, anchor=NW, image=self.photo)
print self.p, self.zf
if __name__ == "__main__":
NaiveTkZoom()
mainloop()
The libraries used and pixel by pixel approach are not the fastest in the world, but will give you enough material to play with.
Also the above code is not very clean.
EDIT2(and3, centered the formula):
Here's another attempt, added translation, but I have a feeling this is not final either (don't have the time to check the formulas). Also the speed of the translation is constant, but that may lead to zooming to slow and showing background (if the point to which you are zooming is too close to the edge).
I've also added a point on the original image so that it is visible what happens with it without need to paint on original image.
#!/bin/env python
from Tkinter import *
import Image
import ImageTk
def markImage(im, p, bg):
pix = im.load()
pix[ p[0], p[1] ] = bg
def naiveZoom(im, p, zf, bg):
out = Image.new(im.mode, im.size)
pix = out.load()
iw, ih = im.size
for x in range(iw):
for y in range(ih):
xorg = x + zf*(p[0]+0.5-x) + zf*(1-zf)*(p[0]-iw/2)
yorg = y + zf*(p[1]+0.5-y) + zf*(1-zf)*(p[1]-ih/2)
if xorg >= 0 and xorg < iw and yorg >= 0 and yorg < ih:
pix[x,y] = im.getpixel( (xorg , yorg) )
else:
pix[x,y] = bg
return out
class NaiveTkZoom:
def __init__(self, parent=None):
root = Tk()
self.im = Image.open('py.jpg')
self.zf = 0.0
self.deltazf = 0.05
self.p = (round(0.3*self.im.size[0]), round(0.3*self.im.size[1]) )
self.bg = 255
markImage(self.im, self.p, self.bg)
canvas = Canvas(root, width=self.im.size[0]+20 , height=self.im.size[1]+20)
canvas.pack()
root.bind('<Key>', self.onKey)
self.canvas = canvas
self.photo = ImageTk.PhotoImage(self.im)
self.item = self.canvas.create_image(10, 10, anchor=NW, image=self.photo)
self.change = False
def onKey(self, event):
if event.char == "+":
if self.zf < 1:
self.zf += self.deltazf
self.change = True
elif event.char == "-":
if self.zf > 0:
self.zf -= self.deltazf
self.change = True
if self.change:
self.out = naiveZoom(self.im, self.p, self.zf, self.bg)
self.photo = ImageTk.PhotoImage(self.out)
self.canvas.delete(self.item)
self.change = False
self.item = self.canvas.create_image(10, 10, anchor=NW, image=self.photo)
print self.p, self.zf
if __name__ == "__main__":
NaiveTkZoom()
mainloop()
There is quite a lot in the above that could be improved. :)
If I understand correctly what you want to do. You can open image in a graphics program (like Gimp) set zoom level at 1 and take a screenshot. Then increase zoom level and take screenshot again etc. Then use mencoder to create AVI from screenshots.
Edit : For art projects you can check this framework : Processing
I make it for 1D, you start by writing the direct transform from original image to zoomed image with your constraints :
As you want a linear transformation, it is in the form :
D( x ) = a x + b
You want :
for z = 0 : D( px ) = px D( px + 1 ) = px + 1
for z = 1 : D( px ) = 0 D( px + 1 ) = sw
This gives :
for z = 0 : a = 1 , b = 0 , D( x ) = x
for z = 1 : a = sw , b = -sw . px , D( x ) = sw.x - sw.px
For all z, you use a linear combination of the two :
D( x ) = z ( sw.x - sw.px ) + ( 1 - z ) ( x ) D( x ) = ( z.sw + 1 - z ).x - z.sw.px
Now you write the inverse function to get the original coordinates from the output coordinates :
ID( xout ) = ( xout + z.sw.px ) / ( z.sw + 1 - z )
Which allows you to fill the output image from the input image. For each output pixel the value is OriginalPixel[ ID( xout ) ] ( And when ID( xout ) is not in [0..sw] you use the background value )
For 2D the idea is similar, but keeping the aspect ratio will need a little more effort.
精彩评论