-1

While using PIL draw rectangles on an image in the canvas. I wish to change the fill rectangle depending on the pixel color of my image in the canvas.

I have referred one of the cross posts (link pasted below) to create rectangles with mouse events in Tkinter.

create_rectangle fills the rectangle with a color specified with mouse events (example: the rectangle is filled with black in this example). Is there a way to logically change the fill color depending on the existing pixel color of the background image? I mean, while drawing a rectangle, I need only the white colored pixels of the background image to be turning red and the rest with a different color.

Drawing rectangle using mouse events in Tkinter

enter image description here

[1]: https://i.stack.imgur.com/W41kX.jpg

def rec_on_button_press(self,event):
    self.start_x = event.x
    self.start_y = event.y
    self.rect=self.image_canvas.create_rectangle(self.x, self.y, 1, 1,fill=self.python_red)
def rec_on_move(self, event):
    curX, curY = (event.x, event.y)
    imagenp= np.array(image)
    if imagenp[curY,curX]==255:
        self.python_red="#EE204D"
    else:
        self.python_red=None
    self.image_canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)

def rec_on_button_release(self, event):
    pass
zym
  • 1
  • 5
  • It sounds not very easy.What did you try ? – jizhihaoSAMA Apr 03 '20 at 08:04
  • I have used numpy to change the image to array and started reading each pixel of the mouse event. This, if statement actually picking the initial mouse position and changing the fill color but not reading all the pixels as mouse being dragged? – zym Apr 03 '20 at 08:42
  • Its not very clear what you want to do. Do you want to make a selection box? If not, can you please mock up an image using GIMP/Paint, etc – Edwin Clement Apr 03 '20 at 08:47
  • Please find the cross post that I have used for drawing rectangles on my existing canvas. https://stackoverflow.com/q/24135170/6864167 – zym Apr 03 '20 at 09:03
  • 1
    Can you create an image with the expected output of the area inside rectangle? – acw1668 Apr 03 '20 at 09:42
  • 1
    I think your idea is not correct.According to your idea(from your code).It will finally generate a rectangle fill with white or fill with red which depends on the pixel where you pressed. – jizhihaoSAMA Apr 03 '20 at 09:49
  • You can use `create_image` to cover the image instead of only using `create_rectangle`. – jizhihaoSAMA Apr 03 '20 at 09:50
  • BTW,If your image is `RGB` format.You should use ``imagenp[curY,curX].all([255,255,255])`` instead of `imagenp[curY,curX]==255`. – jizhihaoSAMA Apr 03 '20 at 09:53
  • My image is of mode "L" unlike RGB. And all my images have either 0 or 255 as pixel value. – zym Apr 03 '20 at 10:02

2 Answers2

1

Principle: Crop the image you select,create it and show it.

The code need to be modified,the example of full code(the example is convert 255(white) to 0(black)):

import tkinter as tk # this is in python 3.4. For python 2.x import Tkinter
from PIL import Image, ImageTk
import numpy as np
import ctypes
ctypes.windll.shcore.SetProcessDpiAwareness(2) # for windows 10

class ExampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.x = self.y = 0
        self.canvas = tk.Canvas(self, width=512, height=512, cursor="cross")
        self.canvas.pack(side="top", fill="both", expand=True)
        self.canvas.bind("<ButtonPress-1>", self.on_button_press)
        self.canvas.bind("<B1-Motion>", self.on_move_press)
        self.canvas.bind("<ButtonRelease-1>", self.on_button_release)

        self.rect = None

        self.start_x = None
        self.start_y = None
        self.python_red = None

        self.im = Image.open(r'ImagePath').convert("L")
        self.move_imageID = None
        self.move_image = None
        self.tk_im = ImageTk.PhotoImage(self.im)
        self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)



    def on_button_press(self, event):
        # save mouse drag start position
        self.start_x = event.x
        self.start_y = event.y
        self.rect = self.canvas.create_rectangle(self.x, self.y,  0, 0, outline="black")

    def on_move_press(self, event):
        self.canvas.delete(self.move_imageID)
        crop_image = self.im.crop((self.start_x,self.start_y,event.x,event.y))
        imageArray = np.array(crop_image)
        if imageArray.shape:
            for i in range(imageArray.shape[0]):
                for j in range(imageArray.shape[1]): # convert  the white pixel to black
                    if imageArray[i,j] == 255:
                        imageArray[i,j] = 0
            self.move_image = Image.fromarray(imageArray)
            self.move_image = ImageTk.PhotoImage(self.move_image)
            self.move_imageID = self.canvas.create_image(self.start_x,self.start_y,anchor="nw",image=self.move_image)
            self.canvas.coords(self.rect,self.start_x,self.start_y,event.x,event.y)
            self.canvas.lift(self.rect)

    def on_button_release(self, event):
        pass


if __name__ == "__main__":
    app = ExampleApp()
    app.mainloop()

Now:

enter image description here

Select area:

enter image description here

jizhihaoSAMA
  • 12,336
  • 9
  • 27
  • 49
  • Thanks for your advice. Two Observations: 1. This suggestion could convert white pixels to black for only the current rectangle. If I try draw too many rectangles, the previously processed / converted pixels gets reset to white but the outline of those rectangles are still there. 2. As I am dealing with images of L format, their pixels are either 0 or 255. I am confused, how can I give a choice of colors (yellow or blue or green or orange etc). Really appreciate your response though. – zym Apr 03 '20 at 13:07
  • @zym 1.No,when you draw the second rectangle,the first one picture will disappear.So that's why I say you need to modify it.2.Why would you want to select color from a grayscale image?You should use `RGB` mode instead of `L` mode.And you also need to get the RGB value of the color you want(like [the RGB value for green](https://www.google.com/search?q=the+rgb+value+for+green) is rgb(0,128,0)). – jizhihaoSAMA Apr 03 '20 at 13:48
  • @ jizhihaoSAMA , Unfortunately, I am getting bunch of such images from my customer. They are generating those images from an imaging system and followed by some processing, which eventually resulted in L mode. – zym Apr 06 '20 at 02:35
  • @zym Well,think it carefully。0-255 only have 256 kinds of colors.RGB mode have 256x256x256 kinds of color.Why would you think you can find green or yellow etc in a grayscale image directly? – jizhihaoSAMA Apr 06 '20 at 02:55
  • @ jizhihaoSAMA, Well the reason is: they ask me to give a color based on the feature I could identify in it. Such images will be used for Machine Learning by someone else. – zym Apr 06 '20 at 05:27
  • `Machine Learning`,So that's should be another question.I'm going to say you have asked how to `Filling colour logically while drawing rectangle Tkinter`,not `How to resume a grayscale image`.(That's also a big problem.)I have done what I do. – jizhihaoSAMA Apr 06 '20 at 12:19
  • Finally I got it done. Please refer to my solution posted here. Thanks @jizhihaoSAMA – zym Apr 09 '20 at 08:27
-1

Principle: Convert image to numpy array and use opencv to draw rectangles. After processing pixel colors convert back to image.

def rec_on_button_press(self,event):
        self.start_x = event.x
        self.start_y = event.y
        self.rect=self.image_canvas.create_rectangle(self.x, self.y, 1, 1,fill=self.python_red)
def rec_on_move(self, event):
        curX, curY = (event.x, event.y)
        cropimage=originalimage.crop((self.start_x,self.start_y,curX,curY))
        cropimagergb=cropimage.convert('RGB')
        cropimagergb.save('crop1.png')
        imagergbcv2=cv2.imread('crop1.png')
        if imagergbcv2.shape[:2]:
            for i in range(imagergbcv2.shape[0]):
                for j in range(imagergbcv2.shape[1]): 
                    color=imagergbcv2[i,j]
                    if (color[0]==255 and color[1]==255 and color[2]==255):
                        imagergbcv2[i,j]=(0,255,0)
        cv2.imwrite('crop2.png',imagergbcv2)
        croppedimage=cv2.imread('crop2.png')
        originalimage[self.start_y:self.start_y+croppedimage.shape[0],self.start_x:self.start_x+croppedimage.shape[1]]=croppedimage
        cv2.imwrite('originalpluscropped.png',originalimage)
    self.image_canvas.coords(self.rect, self.start_x, self.start_y, curX, curY)

def rec_on_button_release(self, event):
    pass
    self.image=Image.open('originalpluscropped.png')
zym
  • 1
  • 5