5

I use this method to resize png images:

Method for converting PNGs to premultiplied alpha

But this image still loses transparency:

import Image, numpy

def resize(filename, img,height, width):
    if filename.endswith(".png"):
        img = img.convert('RGBA')
        premult = numpy.fromstring(img.tostring(), dtype=numpy.uint8)
        alphaLayer = premult[3::4] / 255.0
        premult[::4] *= alphaLayer
        premult[1::4] *= alphaLayer
        premult[2::4] *= alphaLayer
        img = Image.fromstring("RGBA", img.size, premult.tostring())
    img = img.resize((height,width), Image.ANTIALIAS)
    return img

from

https://i.stack.imgur.com/oPV5q.png to https://i.stack.imgur.com/jzmT9.png

Community
  • 1
  • 1
zuroc
  • 85
  • 3
  • 8
  • possible duplicate of [Scale images with PIL preserving transparency and color?](http://stackoverflow.com/questions/13027169/scale-images-with-pil-preserving-transparency-and-color) – Mark Ransom Jan 31 '13 at 20:12
  • 1
    @MarkRansom but his image is not originally in RGBA, it is a paletted image which if converted to RGBA makes everything go wrong. – mmgp Jan 31 '13 at 20:18
  • @mmgp, I see, thanks for clearing that up. I don't have the tools I usually use to evaluate an image at this location, but I suppose opening it in PIL would have shown me that right away. – Mark Ransom Jan 31 '13 at 20:27
  • I think this particular PNG is completely incompatible with PIL. It has a palette with transparency, and the transparency is thrown away as PIL reads the file. – Mark Ransom Jan 31 '13 at 20:49
  • @MarkRansom indeed, I can't manage to read it properly using PIL either. This: http://i.imgur.com/8eKkMdh.png, is the best I can get. – mmgp Jan 31 '13 at 21:02
  • Uhm, I've found the problem. This image can't be read properly without patching PIL. The `tRNS` chunk is read incorrectly. – mmgp Jan 31 '13 at 22:08

3 Answers3

4

This is a more complex function to resize (keeping the transparency): it allows you to use only new width value, both new width and height values or a reference file to get the new size. You can change the resample method too:

## PngResizeTransparency.py
#
## Resize PNG image by keeping the transparency
## thanks to https://stackoverflow.com/users/1453719/nicolas-barbey
#
## Use:
##   - using a reference file to get new sizes:
#        PNG_ResizeKeepTransparency(SourceFile, ResizedFile, RefFile ='YourRefFile.png')
##   - using only the resized width:
#        PNG_ResizeKeepTransparency(SourceFile, ResizedFile, new_width)
##   - using resized width and hight:
#        PNG_ResizeKeepTransparency(SourceFile, ResizedFile, new_width, new_height)
##   - using resample mode: add param resample="NEAREST"/"BILINEAR"/"BICUBIC"/"ANTIALIAS"

from PIL import Image

def PNG_ResizeKeepTransparency(SourceFile, ResizedFile, new_width=0, new_height=0, resample="ANTIALIAS", RefFile =''):
    # needs PIL
    # Inputs:
    #   - SourceFile  = initial PNG file (including the path)
    #   - ResizedFile = resized PNG file (including the path)
    #   - new_width   = resized width in pixels; if you need % plz include it here: [your%] *initial width
    #   - new_height  = resized hight in pixels ; default = 0 = it will be calculated using new_width
    #   - resample = "NEAREST", "BILINEAR", "BICUBIC" and "ANTIALIAS"; default = "ANTIALIAS"
    #   - RefFile  = reference file to get the size for resize; default = ''

    img = Image.open(SourceFile) # open PNG image path and name
    img = img.convert("RGBA")    # convert to RGBA channels
    width, height = img.size     # get initial size

    # if there is a reference file to get the new size
    if RefFile != '':
        imgRef = Image.open(RefFile)
        new_width, new_height = imgRef.size
    else:
        # if we use only the new_width to resize in proportion the new_height
        # if you want % of resize please use it into new_width (?% * initial width)
        if new_height == 0:
            new_height = new_width*width/height

    # split image by channels (bands) and resize by channels
    img.load()
    bands = img.split()
    # resample mode
    if resample=="NEAREST":
        resample = Image.NEAREST
    else:
        if resample=="BILINEAR":
            resample = Image.BILINEAR
        else:
            if resample=="BICUBIC":
                resample = Image.BICUBIC
            else:
                if resample=="ANTIALIAS":
                    resample = Image.ANTIALIAS
    bands = [b.resize((new_width, new_height), resample) for b in bands]
    # merge the channels after individual resize
    img = Image.merge('RGBA', bands)
    # save the image
    img.save(ResizedFile)
    return

#######################################################

if __name__ == "__main__":
    sFile = './autumn-png-leaf.png'
    # resize using new width value (new height is calculated by keeping image aspect)
    PNG_ResizeKeepTransparency(sFile, sFile[:-4]+'_resized.png', 400)
    # resize using a reference file to get the new image dimension 
    PNG_ResizeKeepTransparency(sFile, sFile[:-4]+'_resized.png', RefFile = 'autumn-png-leaf_starry-night-van-gogh_fchollet_10.png')
muntisa
  • 41
  • 1
  • Awesome! but instead of the bunch of `if/elif` statements, you could've done: `getattr(Image, resample, Image.ANTIALIAS)` – Lemayzeur Oct 06 '22 at 14:50
3

The problem is unrelated to the linked question, instead you have to patch PIL such that it reads the tRNS PNG chunk correctly. PIL assumes a single value for this chunk, but this image shown has a transparency description for each value in the palette. After that is handled, then it is simple to solve the problem: convert the image to the 'LA' mode and resize:

import sys
from PIL import Image

img = Image.open(sys.argv[1])
pal = img.getpalette()
width, height = img.size
actual_transp = img.info['actual_transparency'] # XXX This will fail.

result = Image.new('LA', img.size)

im = img.load()
res = result.load()
for x in range(width):
    for y in range(height):
        t = actual_transp[im[x, y]]
        color = pal[im[x, y]]
        res[x, y] = (color, t)

result.resize((64, 64), Image.ANTIALIAS).save(sys.argv[2])

So we go from this enter image description here, to this: enter image description here

The PIL patch for this specific situation is very simple actually. Open your PIL/PngImagePlugin.py, go to the function chunk_tRNS, enter the if statement that checks for im_mode == "P" and the subsequent check for i >= 0, then add the line self.im_info["actual_transparency"] = map(ord, s).

mmgp
  • 18,901
  • 3
  • 53
  • 80
  • The `ImagePalette` class already has a `mode` associated with it, if only PIL would use a proper `'RGBA'` mode everything would be much better. Your solution works for this special case of a monochrome image but there are many other valid images where it would fail. – Mark Ransom Jan 31 '13 at 22:50
  • @MarkRansom but this transparency data is not available in the `PLTE` chunk -- where PIL builds the palette for the png image. So it wouldn't matter if the palette was simply built as `RGBA`, it has to read the `tRNS` chunk properly and possibly combine it in the palette. So the "patch" included is only to read the `tRNS` chunk properly, any png image in mode `P` that depends on it would work with this patch. What kind of png image you had in mind that this fails ? – mmgp Jan 31 '13 at 22:56
  • Your answer makes the assumption that the palette is a gray-scale palette, which isn't necessarily the case. Converting to `'RGBA'` instead of `'LA'` would fix that, but then the result wouldn't be 8-bit anymore. – Mark Ransom Jan 31 '13 at 23:08
  • @MarkRansom but that part of the answer is irrelevant to the problem, the image could contain a `RGB` palette and then it would work just fine by switching from `LA` to `RGBA`. – mmgp Jan 31 '13 at 23:14
1

I have fixed this problem, the nice hack from mmgp is no longer necessary. PIL will now read and apply the transparency properly.

https://github.com/d-schmidt/Pillow/commit/5baa1ac1b8d41fcedce7b12ed1c4a8e87b4851bc

dss
  • 11
  • 2
  • You mean the latest bleeding Pillow will apply it right ? I'm not sure what you mean by "hack" here, since you are doing `self.im_info["transparency_palette"] = s` in that code too. How is reading the chunk correctly a hack ? – mmgp Feb 01 '13 at 20:08
  • No, as you've already noticed, I'm using that idea, too. But in my implementation I'm not copying the image and looping over it. While loading the transparency gets applied the ways it is intended to be. The bottom part of your solution is a "hack". What do you mean by bleeding? – dss Feb 01 '13 at 21:20
  • I meant bleeding edge version. Is it just coincidence that you patched `chunk_tRNS` exactly like I did but only 5 hours ago ? I'm not claiming this is revolutionary in any way, but we can see how sad this is: person A posts an answer, person B applies the patch from the earlier answer plus an epsilon modification, person B bad talks about person A because of +epsilon modification that person A didn't do. Lastly, I didn't bother "correctly" patching PIL as it involves modifying `_imaging.c` (and thus involves recompiling), because all I was doing was showing PIL reads that png chunk incorrectly – mmgp Feb 01 '13 at 21:30
  • Oh, by calling it a "hack" I did by no means say anything bad in my opinion. Hacks are awesome. – dss Feb 01 '13 at 21:59
  • I used your knowledge of the chunk thing, it isn't coincidence and I'm not going to claim, that I've discovered the flaw. zuroc posted this linked question as an issue over at pillow. I've just taken a look at it, a good look at your solution and implemented it directly into Pillow, where it will be recompiled. MarkRansom also already mentioned the small flaw in your solution forcing the image into grayscale or into rgb to apply the transparency. The internal bugfix doesn't have that problem. This is just the proper solution Mark mentioned above. – dss Feb 01 '13 at 22:05
  • Fine. But please note that the remarks given by MarkRansom are invalid: the image is grayscale, I'm not forcing anything. I merely included an example based on the image included in the question, it can be easily modified to work with a colored palette. Anyway, I took "hack" as something bad because the way your answer is worded (i.e., it was my own interpretation). It would be great if the actual patch could be included in PIL for the next release too, by the way. – mmgp Feb 01 '13 at 22:09
  • I'm sure it will be included. The color thing is true, your solution is working well for the one image the TO linked. – dss Feb 01 '13 at 22:12