# GPixPod - the free and open source way to manage photos on your POD! # Copyright (C) 2006 Flavio Gargiulo (FLAGAR.com) # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Image formats conversions import struct, gtk def rotateCCW(pixels, width, height, pixelbytes=2): """ Rotate counter-clockwise a pixels data stream with given original width and height """ pixels_len = len(pixels) if pixels_len != width*pixelbytes*height: raise "Width and/or height seem not correct for the pixels data stream received" newpixels = '' row = width*pixelbytes for wpos in range(width*pixelbytes, 0, -pixelbytes): for hpos in range(0, pixels_len, row): newpixels += pixels[hpos+wpos-pixelbytes:hpos+wpos] return newpixels def rotateCW(pixels, width, height, pixelbytes=2): """ Rotate clockwise a pixels data stream with given original width and height """ # pixels_len = len(pixels) # if pixels_len != width*pixelbytes*height: # raise "Width and/or height seem not correct for the pixels data stream received" # newpixels = '' # row = width*pixelbytes # for wpos in range(0, row+1, pixelbytes): # for hpos in range(width*pixelbytes*(height-1), -1, -row): # newpixels += pixels[hpos+wpos:hpos+wpos+pixelbytes] # return newpixels result = rotateCCW(pixels, width, height, pixelbytes) result = rotateCCW(result, height, width, pixelbytes) result = rotateCCW(result, width, height, pixelbytes) return result def getPixbuf(filename, width, height): """ Return a gdk-pixbuf without alpha channel and scaled to the specified dimensions keeping ratio """ # Opening and scaling keeping ratio pixbuf = gtk.gdk.pixbuf_new_from_file(filename) height_with_ratio = pixbuf.get_height()*width/pixbuf.get_width() if height > height_with_ratio: pixbuf = pixbuf.scale_simple(width, height_with_ratio, gtk.gdk.INTERP_BILINEAR) bgpixbuf = pixbuf.composite_color_simple(width, height, gtk.gdk.INTERP_BILINEAR, 0, 2, 0, 0) pixbuf.copy_area(0, 0, width, height_with_ratio, bgpixbuf, 0, (height-height_with_ratio)/2) pixbuf = bgpixbuf elif height < height_with_ratio: width_with_ratio = pixbuf.get_width()*height/pixbuf.get_height() pixbuf = pixbuf.scale_simple(width_with_ratio, height, gtk.gdk.INTERP_BILINEAR) bgpixbuf = pixbuf.composite_color_simple(width, height, gtk.gdk.INTERP_BILINEAR, 0, 2, 0, 0) pixbuf.copy_area(0, 0, width_with_ratio, height, bgpixbuf, (width-width_with_ratio)/2, 0) pixbuf = bgpixbuf else: pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR) rgbdata = pixbuf.get_pixels() # Managing alpha channel if pixbuf.get_has_alpha(): rgbdata_noalpha = '' for apos in range(0, len(rgbdata), 4): rgbdata_noalpha += rgbdata[apos:apos+3] rgbdata = rgbdata_noalpha pixbuf = gtk.gdk.pixbuf_new_from_data(rgbdata, gtk.gdk.COLORSPACE_RGB, False, 8, width, height, width*3) return pixbuf def toRGB565(filename, width, height, swap_bytes=True, rotate=False): """ Resize an image and convert it to RGB565 swapped bytes format """ thumbsizes = [(50, 41), (320, 240), (130, 88)] # 4100, 153600, 22880 dim # iPod 5G thumbsizes.extend([(220, 176), (42, 30)]) # 77440, 2520 dim # iPod Photo, Color thumbsizes.extend([(176, 132), (42, 37)]) # 46464, 3108 dim # iPod Nano if (width, height) not in thumbsizes: print "Size not recognized" pixbuf = getPixbuf(filename, width, height) rgbdata = pixbuf.get_pixels() if rotate: rgbdata_rotated = rotateCCW(rgbdata, width, height, 3) pixbuf = gtk.gdk.pixbuf_new_from_data(rgbdata_rotated, gtk.gdk.COLORSPACE_RGB, False, 8, height, width, height*3) rgbdata = pixbuf.get_pixels() width = pixbuf.get_width() height = pixbuf.get_height() rowstride = pixbuf.get_rowstride() # With GDK-PIXBUF occasionally happens that rowstride != width*3. # It always happens when scaling either to 50x41 or to 130x88. # I have not figured out why yet. # This is required! Thus, we delete the exceeding bytes. if rowstride > (width*3): newrgbdata = '' right_row = width*3 diff_row = rowstride - right_row for pos in range(0, len(rgbdata), rowstride): newrgbdata += rgbdata[pos:pos+right_row] rgbdata = newrgbdata rgb565data = '' # Actually converting to RGB565 swapped bytes for pos in range(0, len(rgbdata), 3): r = struct.unpack('B', rgbdata[pos:pos+1])[0] g = struct.unpack('B', rgbdata[pos+1:pos+2])[0] b = struct.unpack('B', rgbdata[pos+2:pos+3])[0] r = r >> 3 g = g >> 2 b = b >> 3 g2 = g & 7 byte1 = (r << 3) + (g >> 3) # RRRRRGGG byte2 = (g2 << 5) + b # GGGBBBBB if swap_bytes: rgb565data += struct.pack('B', byte2) rgb565data += struct.pack('B', byte1) else: rgb565data += struct.pack('B', byte1) rgb565data += struct.pack('B', byte2) return rgb565data def fromRGB565(filename, width, height, swap_bytes=True, rotate=False): """ Convert an image from RGB565 swapped bytes format to PNG """ thumbsizes = {4100:(50, 41), 153600:(320, 240), 22880:(130, 88)} # Adding iPod Photo, Color thumb dimensions thumbsizes[77440] = (220, 176) thumbsizes[2520] = (42, 30) # Adding iPod Nano thumb dimensions thumbsizes[46464] = (176, 132) thumbsizes[3108] = (42, 37) # Begin processing origthumb = open(filename, 'rb') origthumb.seek(0, 2) origthumbsize = origthumb.tell() origthumb.seek(0) rgbdata = '' try: width = thumbsizes[origthumbsize][0] height = thumbsizes[origthumbsize][1] except KeyError: print "Not recognized input format" for pos in range(0, origthumbsize, 2): if swap_bytes: byte2 = struct.unpack('B', origthumb.read(1))[0] byte1 = struct.unpack('B', origthumb.read(1))[0] else: byte1 = struct.unpack('B', origthumb.read(1))[0] byte2 = struct.unpack('B', origthumb.read(1))[0] r = byte1 >> 3 b = byte2 & 31 g1 = byte1 & 7 g2 = byte2 >> 5 g1 = g1 << 3 g = g1 + g2 r = r << 3 g = g << 2 b = b << 3 rgbdata += struct.pack('B', r) rgbdata += struct.pack('B', g) rgbdata += struct.pack('B', b) origthumb.close() if rotate: pixbuf = gtk.gdk.pixbuf_new_from_data(rotateCW(rgbdata, width, height, 3), gtk.gdk.COLORSPACE_RGB, False, 8, height, width, (height*3)) else: pixbuf = gtk.gdk.pixbuf_new_from_data(rgbdata, gtk.gdk.COLORSPACE_RGB, False, 8, width, height, (width*3)) pixbuf.save('%s.png' % filename, 'png') def unsign(n): """ Round a number to unsigned integer """ # I don't know if there is an equivalent built-in one in Python! return abs(int(n)) def limit(n): """ Keep a number within the range 0-255 """ if n < 0: return 0 elif n > 255: return 255 else: return n def toInterlacedUYVY(filename): """ Resize an image to 720x480 and convert it to interlaced UYVY (YUV 4:2:2) format """ # Converting from PNG, JPG to RGB #pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 720, 480) #pixbuf = pixbuf.composite_color_simple(720, 480, gtk.gdk.INTERP_TILES, 255, 2160, 0, 0) pixbuf = getPixbuf(filename, 720, 480) rgbdata = pixbuf.get_pixels() # Converting from RGB to RGB interlaced irgbdata1 = '' irgbdata2 = '' pos = 0 rowstride = 720*3 halfsize = 691200/2 while pos < len(rgbdata): irgbdata1 += rgbdata[pos:pos+rowstride] pos += rowstride irgbdata2 += rgbdata[pos:pos+rowstride] pos += rowstride irgbdata = irgbdata1 + irgbdata2 # Converting from RGB interlaced to YUV 4:2:2 (UYVY) yuvdata = '' for pos in range(0, len(irgbdata), 6): R0 = struct.unpack('B', irgbdata[pos:pos+1])[0] G0 = struct.unpack('B', irgbdata[pos+1:pos+2])[0] B0 = struct.unpack('B', irgbdata[pos+2:pos+3])[0] R1 = struct.unpack('B', irgbdata[pos+3:pos+4])[0] G1 = struct.unpack('B', irgbdata[pos+4:pos+5])[0] B1 = struct.unpack('B', irgbdata[pos+5:pos+6])[0] U0 = ((R0*-38 - G0*74 + B0*112 + 128) >> 8) + 128 Y0 = ((R0*66 + G0*129 + B0*25 + 128) >> 8) + 16 V0 = ((R0*112 - G0*94 - B0*18 + 128) >> 8) + 128 Y1 = ((R1*66 + G1*129 + B1*25 + 128) >> 8) + 16 yuvdata += struct.pack('B', U0) yuvdata += struct.pack('B', Y0) yuvdata += struct.pack('B', V0) yuvdata += struct.pack('B', Y1) return yuvdata def fromInterlacedUYVY(filename): """ Convert an image from interlaced UYVY (YUV 4:2:2) format to PNG """ origthumb = open(filename, 'rb') yuvdata = origthumb.read() origthumb.close() # From YUV to RGB... rgbdata = '' for pos in range(0, 691200, 4): U0 = U1 = struct.unpack('B', yuvdata[pos:pos+1])[0] Y0 = struct.unpack('B', yuvdata[pos+1:pos+2])[0] V0 = V1 = struct.unpack('B', yuvdata[pos+2:pos+3])[0] Y1 = struct.unpack('B', yuvdata[pos+3:pos+4])[0] # Maybe formulas should be converted to integers, to gain speed R0 = (Y0 - 16)*1.164 + (V0 - 128)*1.596 G0 = (Y0 - 16)*1.164 - (V0 - 128)*0.813 - (U0 - 128)*0.391 B0 = (Y0 - 16)*1.164 + (U0 - 128)*2.018 R1 = (Y1 - 16)*1.164 + (V0 - 128)*1.596 G1 = (Y1 - 16)*1.164 - (V0 - 128)*0.813 - (U0 - 128)*0.391 B1 = (Y1 - 16)*1.164 + (U0 - 128)*2.018 rgbdata += struct.pack('B', limit(R0)) rgbdata += struct.pack('B', limit(G0)) rgbdata += struct.pack('B', limit(B0)) rgbdata += struct.pack('B', limit(R1)) rgbdata += struct.pack('B', limit(G1)) rgbdata += struct.pack('B', limit(B1)) # Deinterlacing... newrgbdata = '' halfsize = len(rgbdata)/2 rowstride = 720*3 for pos in range(0, halfsize, rowstride): newrgbdata += rgbdata[pos:pos+rowstride] newrgbdata += rgbdata[pos+halfsize:pos+halfsize+rowstride] # Converting RGB to PNG pixbuf = gtk.gdk.pixbuf_new_from_data(newrgbdata, gtk.gdk.COLORSPACE_RGB, False, 8, 720, 480, (720*3)) pixbuf.save('%s.png' % filename, 'png')