BennySunshine BennySunshine - 18 days ago 7
Python Question

Rescale an Image using Python3.2

I'm working on a python code that takes an image name from the command line, and prints it in a window, rescaled to the user's likings, i.e., the input python3.2 resize.py image.gif 2 3 would take image.gif and double the width and triple the height. I've written a code for quadrupling an image:

import sys
from cImage import *

def main():
oldImage = FileImage(sys.argv[1])
width = oldImage.getWidth()
height = oldImage.getHeight()
myWin = ImageWin("Old Image", width, height)
myNewWin = ImageWin("Quadrupled Image", width*4, height*4)
newImage = EmptyImage(width*4, height*4)

for r in range(width):
for c in range(height):
pixel = oldImage.getPixel(r, c)
newImage.setPixel(4*r, 4*c, pixel)
newImage.setPixel(4*r, 4*c+1, pixel)
newImage.setPixel(4*r, 4*c+2, pixel)
newImage.setPixel(4*r, 4*c+3, pixel)
newImage.setPixel(4*r+1, 4*c, pixel)
newImage.setPixel(4*r+1, 4*c+1, pixel)
newImage.setPixel(4*r+1, 4*c+2, pixel)
newImage.setPixel(4*r+1, 4*c+3, pixel)
newImage.setPixel(4*r+2, 4*c, pixel)
newImage.setPixel(4*r+2, 4*c+1, pixel)
newImage.setPixel(4*r+2, 4*c+2, pixel)
newImage.setPixel(4*r+2, 4*c+3, pixel)
newImage.setPixel(4*r+3, 4*c, pixel)
newImage.setPixel(4*r+3, 4*c+1, pixel)
newImage.setPixel(4*r+3, 4*c+2, pixel)
newImage.setPixel(4*r+3, 4*c+3, pixel)
oldImage.draw(myWin)
newImage.draw(myNewWin)
myWin.exitOnClick()
myNewWin.exitOnClick()

main()


But I am having trouble trying to figure out how to edit my code so it scales it as requested. I feel that I should probably be able to implement a for loop, but I'm having a hard time getting things to work.

Any help would be much appreciated!
EDIT:

I am working with the following modified code:

from cImage import *
import sys

def main():
oldImage = FileImage(sys.argv[1])
oldWidth = oldImage.getWidth()
oldHeight = oldImage.getHeight()

widthScalar = int(sys.argv[2])
heightScalar = int(sys.argv[3])

newWidth = oldWidth * widthScalar
newHeight = oldHeight * heightScalar

myWin = ImageWin("Old Image", oldWidth, oldHeight)
myNewWin = ImageWin("Scaled Image", newWidth, newHeight)
newImage = EmptyImage(newWidth, newHeight)

for r in range(oldHeight*heightScalar):
for c in range(oldWidth*widthScalar):
pixel = oldImage.getPixel(r // heightScalar, c // widthScalar)
newImage.setPixel(r, c, pixel)

oldImage.draw(myWin)
newImage.draw(myNewWin)
myWin.exitOnClick()
myNewWin.exitOnClick()

main()


I've also tried using the more complicated method:

for r in range(oldHeight):
for c in range(oldWidth):
pixel = oldImage.getPixel(r,c)
for i in range(heightScalar):
for j in range(widthScalar):
myNewImage.setPixel((heightScalar*r)+i, (widthScalar*c)+j, pixel)


I've tested it with scalars that are equal (i.e. 3 x 3) and it works fine with a square image, but when I try to change one of them, I get an error:


ValueError: Pixel index out of range.


When I try with a not square image, I got the error that my coordinates were out of range.

I'm kind of at a loss of what to do... Thank you abarnert for all of your help!
——————————————————————————————————————————————
I've gone and tested both my quadruple.py and resize.py, and with square images they work, but when I put a none square image in, I get the following:


pixel = oldImage.getPixel(r, c)

File "/Users/jakebenedict/CPS/Lab6/cImage.py", line 313, in getTkPixel

p = [int(j) for j in self.im.get(x,y).split()]

File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/tkinter/init.py", line 3260, in get

return self.tk.call(self.name, 'get', x, y)

_tkinter.TclError: pyimage1 get: coordinates out of range


Not really sure why its saying that...

Answer

I feel that I should probably be able to implement a for loop

You're on the right track. But to do it this way, you actually need two for loops, nested.*

for yoffset in range(yscale):
    for xoffset in range(xscale):
        newImage.setPixel(yscale*r + yoffset, xscale*c + xoffset, pixel)

Why?

Well, first, for each row, you've got 4 lines of code, like this:

newImage.setPixel(4*r, 4*c, pixel)
newImage.setPixel(4*r, 4*c+1, pixel)
newImage.setPixel(4*r, 4*c+2, pixel)
newImage.setPixel(4*r, 4*c+3, pixel)

So, to abstract that into a loop, you want that 4*c+n to be a loop where n goes from 0 to 3, like this:

for n in range(4):
    newImage.setPixel(4*r, 4*c+n, pixel)

Now you can replace the 4 with xscale, and you're done. Now just repeat the same trick with the rows (and give n a better name so you can keep the row and column offsets straight) and you have the code above.


However, if you turn your algorithm around, there's a possibly simpler way to look at this. Instead of iterating over the original pixels, and figuring out the 16 new pixels that each one maps to, why not iterate over the new pixels, and figure out the 1 old pixels that each one (non-uniquely) maps to?

for r in range(height*yscale):
    for c in range(width*xscale):
        pixel = oldImage.getPixel(r // yscale, c // xscale)
        newImage.setPixel(r, c, pixel)

Besides being simpler, this has another advantage: You can allow the user to specify a float scale value, and the loop works with no changes.


* You don't really need two loops; you can always write a single loop over the cartesian product, like for (yoffset, xoffset) in itertools.product(range(yscale), range(xscale)):. But that's really just saying the same thing in a more complicated way. There are definitely cases where product is useful, but this isn't one of them.

Comments