1

What is the easiest way to change the colour of the whole image with a RGB value? I have tried wand, however the documentation didn't make much sense to me, and I can only find changing the intensity of the colours in the Pillow documentation.

I tried multiple solutions online, however either they didn't do what I wanted, or were out of date and didn't work.

I want it so that the whole image gets tinted and I can control the tint by changing the RGB colour, bit like this:

http://cdn.makeuseof.com/wp-content/uploads/2012/11/Folder-Colorizer-Color-Manager.jpg?69fac7

I can implement the wheel myself, however the actual changing colour part is confusing me. Hopefully it will be an easy solution. :)

martineau
  • 119,623
  • 25
  • 170
  • 301
VOT Productions
  • 136
  • 1
  • 9
  • The answer to [_Python: Colorize image while preserving transparency with PIL_](http://stackoverflow.com/questions/12251896/python-colorize-image-while-preserving-transparency-with-pil) might be useful to you. – martineau Mar 29 '15 at 18:29
  • Thank you, I converted that to Python 3 and it worked :D – VOT Productions Mar 30 '15 at 15:26
  • If the other answer helped you, please consider up-voting it. Thanks. – martineau Mar 30 '15 at 15:27
  • I will as soon I get enough rep :) – VOT Productions Mar 30 '15 at 17:21
  • @martineau Your code seems to fall apart on this image. http://i.imgur.com/Dn3CeZB.png. If I want to make the image #383D2D, I get this http://i.imgur.com/EMnts1k.png instead of a more expected dark tint. Apart from this problem, the code works great :D – VOT Productions Mar 31 '15 at 19:16
  • The results look OK to me using your image and that tint value under Python 2.7.9 (see [before and after image](https://dl.dropboxusercontent.com/u/5508445/stackoverflow/colorize_result_comparision.png)) — so I strongly suspect the problem is with your Python 3 conversion. What did you do? – martineau Mar 31 '15 at 21:09
  • Is the blue what happens with my tint value? I thought #383D2D was an dark olive green sort of colour. I used 2to3 to convert it to Python 3, and changed the imports to be Pillow compatible. – VOT Productions Mar 31 '15 at 21:17
  • By the way, do I have to put @martineau in every post that I do or do you get the messages anyway? I'm quite new to Stack Overflow :) – VOT Productions Mar 31 '15 at 21:39
  • My mistake, I forgot to change the tint color to what you were using. Never-the-less it seems to work OK under Python 2 (I'm also using `pillow` nowadays), so it's got to have something to do with the conversion -- 2to3 isn't perfect. Yes, you have to put the @martineau in every message to me since these comments are under your question rather than an answer I posted. – martineau Mar 31 '15 at 21:43
  • I'll try converting it to Python 3 myself. – martineau Mar 31 '15 at 21:45
  • Thank you so much for your support so far, unlike the guy below us. I feel slightly guilty now seeing that I can't upvote your question still :(. To make sure I'm not making a silly mistake, this is what happens with my converted code: http://i.imgur.com/YIUuTmO.png. Again, thank you. – VOT Productions Mar 31 '15 at 22:21

2 Answers2

4

Here's a Python 3 version of the code in my other answer. It's almost identical except for the imports which had to be changed in order to use the pillow fork of the PIL (because only it supports Python 3). The other changes I made were changing print statements into function calls and where the map() function is used to create the luts look-up table variable.

from PIL import Image
from PIL.ImageColor import getcolor, getrgb
from PIL.ImageOps import grayscale

def image_tint(src, tint='#ffffff'):
    if Image.isStringType(src):  # file path?
        src = Image.open(src)
    if src.mode not in ['RGB', 'RGBA']:
        raise TypeError('Unsupported source image mode: {}'.format(src.mode))
    src.load()

    tr, tg, tb = getrgb(tint)
    tl = getcolor(tint, "L")  # tint color's overall luminosity
    if not tl: tl = 1  # avoid division by zero
    tl = float(tl)  # compute luminosity preserving tint factors
    sr, sg, sb = map(lambda tv: tv/tl, (tr, tg, tb))  # per component
                                                      # adjustments
    # create look-up tables to map luminosity to adjusted tint
    # (using floating-point math only to compute table)
    luts = (tuple(map(lambda lr: int(lr*sr + 0.5), range(256))) +
            tuple(map(lambda lg: int(lg*sg + 0.5), range(256))) +
            tuple(map(lambda lb: int(lb*sb + 0.5), range(256))))
    l = grayscale(src)  # 8-bit luminosity version of whole image
    if Image.getmodebands(src.mode) < 4:
        merge_args = (src.mode, (l, l, l))  # for RGB verion of grayscale
    else:  # include copy of src image's alpha layer
        a = Image.new("L", src.size)
        a.putdata(src.getdata(3))
        merge_args = (src.mode, (l, l, l, a))  # for RGBA verion of grayscale
        luts += tuple(range(256))  # for 1:1 mapping of copied alpha values

    return Image.merge(*merge_args).point(luts)

if __name__ == '__main__':
    import os
    import sys

    input_image_path = 'Dn3CeZB.png'
    print('tinting "{}"'.format(input_image_path))

    root, ext = os.path.splitext(input_image_path)
    suffix = '_result_py{}'.format(sys.version_info[0])
    result_image_path = root+suffix+ext

    print('creating "{}"'.format(result_image_path))
    result = image_tint(input_image_path, '#383D2D')
    if os.path.exists(result_image_path):  # delete any previous result file
        os.remove(result_image_path)
    result.save(result_image_path)  # file name's extension determines format

    print('done')

Here's before and after images. The test image and tint color are the same as what you said you were using when you encountered the problem. The results look very similar to the Py2 version, to yours, and OK to me...am I missing something?

screenshot of before and after images

Community
  • 1
  • 1
martineau
  • 119,623
  • 25
  • 170
  • 301
  • I will try your converted code tomorrow as I currently don't have access to my machine, thank you for converting it. In the meantime, here is another example on the bug I posted in comments above, but forgot to mention your username: http://i.imgur.com/YIUuTmO.png – VOT Productions Mar 31 '15 at 22:47
  • OK, I think I see why. Does this only regulates the colour of this picture, but not the contrast and brightness of it? If so, that's not a problem, I can just add in two extra sliders. – VOT Productions Mar 31 '15 at 22:58
  • It tries to preserve luminosity which is difficult to do when you tint with such a dark blue color (the human eye is least sensitive to blue). What's shown in the http://i.imgur.com/YIUuTmO.png screenshot are the results I would expect using such a dark tint color. Another way to think about what it does is it will turn white in the original image into the tint color and everything else would come out being no brighter than than. – martineau Mar 31 '15 at 23:07
  • It would be easier and faster to lighten the tint color than adjust the contrast and brightness of the whole image afterwards. – martineau Mar 31 '15 at 23:25
  • I see, it doesn't know what to do with a really dark colour. It's not really a problem though. Thanks for helping me :) – VOT Productions Apr 01 '15 at 15:01
  • How would you suggest it handle dark colors? – martineau Apr 01 '15 at 16:52
  • your code has been working fantastically up to this point. It seems that the code has a memory leak (but I'm not sure, I'm a novice when it comes to debugging memory leaks :P.) The `image_tint` function is called 6 times every time it's ran, meaning it leaks 100kb. To test this, try putting a for loop with a range of 500, you'll see the memory usage climb steadily. Thanks, VOT. – VOT Productions Apr 12 '15 at 18:58
  • What do you mean it's called 6 times every time it's ran? Regardless, in Python one doesn't allocate and deallocate memory like in C, it all handled automatically with via garbage-collection. On the other hand it's quite possible there's a memory leak in `pillow` which is partially written in C. Also I have another Python 3 version of this code I worked on after posting this answer were I optimized things a bit. When I get a chance I'll update this answer with the changes. Regardless, if even my current answer helped you, I would appreciate an up-vote for it. – martineau Apr 12 '15 at 19:34
  • Martineau, you're alive! What I meant is in my application, that tint function is called 6 times every time someone presses a button, so in my case this memory leak is more severe due to how my program works. Anyway, sorry for not upvoting, but I still have 13 reputation as I don't use Stackoverflow that often, and I promise I will upvote as soon as possible. The cleaned up version would be nice as well :). I did some more testing with objgraph and it seems the list object count were going up and up http://pastebin.com/NdtmdzFF, but I don't know if that's bad. Thanks, VOT. – VOT Productions Apr 12 '15 at 19:42
  • 1
    Yes, thanks. I updated my answer so it now uses tuples instead of lists when creating the main look-up table because they're smaller and accessed more quickly I believe. The function creates big one of composed of 3 to 4 256 entry tuples each time it's called and they're all local variables which should be released and garbage collected automatically when it returns--along with a temporary b&w version of the image that's also created. It will be interesting to see what if any affect there is from the modification with respect to the increasing list object count issue you mentioned. – martineau Apr 13 '15 at 02:46
  • http://pastebin.com/94CfJgzq (code that essentially does what my program does) - It still seems to be going up. Interestingly, when measuring the memory with psutil, it seems to be going up very slowly sometimes by 4kb, and then suddenly it goes up by 100kb. If you want my entire program code (it's about 1000 lines), please ask. Thanks, VOT. – VOT Productions Apr 13 '15 at 10:33
  • I'll look into when I have a chance, but unfortunately I've very busy this week. so it'll be a while—and there's probably nothing I can do about it anyway. Therefore I suggest that you file a bug report with the `pillow` project, because that's what's doing any leaking that may be occurring. It very easy for C extension code using the Python/C API to forget to decrement an object's reference count when it's done with it which will prevent it from being garbage-collected properly when it's no longer in use. – martineau Apr 13 '15 at 11:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/75099/discussion-between-vot-productions-and-martineau). – VOT Productions Apr 13 '15 at 11:40
  • 1
    Turns out this a leak in `.putdata()`, people on my GitHub issue are able to reproduce it https://github.com/python-pillow/Pillow/issues/1187 – VOT Productions Apr 16 '15 at 12:54
0
import Image
import numpy as nump

img = Image.open('snapshot.jpg')

# In this case, it's a 3-band (red, green, blue) image
# so we'll unpack the bands into 3 separate 2D arrays.
 r, g, b = nump.array(img).T

  # Let's make an alpha (transparency) band based on where blue is < 100
  a = nump.zeros_like(b)
  a[b < 100] = 255

 # Random math... This isn't meant to look good...
 # Keep in mind that these are unsigned 8-bit integers, and will overflow.
   # You may want to convert to floats for some calculations.
  r = (b + g) * 5

    # Put things back together and save the result...
  img = Image.fromarray(nump.dstack([item.T for item in (r,g,b,a)]))

   img.save('out.png')

Like this., you can use numpy.

OR

you can use Pillow. [lnk] (https://pillow.readthedocs.org/)

MeshBoy
  • 662
  • 5
  • 9
  • Unfortunately, it seems that your solution didn't seem to work. It seems to be complaining about this error: `File "test1.py", line 8, in r, g, b = nump.array(img).T ValueError: too many values to unpack (expected 3)` – VOT Productions Mar 29 '15 at 18:20