Image Filter everything but a range of similar colors Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Image Filter everything but a range of similar colors

Post by Spitzi » 06 Aug 2022, 16:16

I was reading the following thread:

viewtopic.php?style=1&f=76&t=98341

where XTRA posted this nice ColorFilter-Function that takes pBitmap and replaces everything but a certain color. Thanks for that, works great and is very fast.

I was wondering if anyone knows a way to have it replace everything but "a certain color and similar colors"? XTRA's code is over my head for me to modify it, and I understand it involves machine code to be fast...

Thanks in advance. Simon


iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Image Filter everything but a range of similar colors

Post by iseahound » 08 Aug 2022, 16:07

Call this line:

Code: Select all

DllCall(ColorFilterMCode, "uint", scan, "int", w, "int", h, "int", Stride, "uint", TargetColor, "uint", ReplaceColor, "cdecl")
Multiple times. Use a different TargetColor for each call. No machine code needed, just plain AutoHotkey is good to modify this code!

User avatar
boiler
Posts: 16925
Joined: 21 Dec 2014, 02:44

Re: Image Filter everything but a range of similar colors

Post by boiler » 08 Aug 2022, 16:38

An issue with that approach is that if you want to use the equivalent of just 10 shades of allowable variation, it results in a large number of different color codes (almost 10000). That's because the range gets applied per RGB component, so a range of 10 results in 21 x 21 x 21 = 9261 different shades (21 each because it's the nominal itself plus 10 in either direction). So you'd have to loop through that DLL call 9261 times. And a range of 10 isn't even that wide of a range. That range is barely even noticeable to a human eye, and it goes up exponentially as you widen it. Just making it range of 20 would result in 68921 different hues to check. I'm guessing it wouldn't be very fast to loop that many times.

A modification of @Xtra's C function would just have it check to see if each component is within the allowable range instead of being equal to the specified color. It wouldn't even need to loop.

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Image Filter everything but a range of similar colors

Post by Spitzi » 10 Aug 2022, 08:14

thanks @iseahound and @boiler for your answers

Yeah, calling the DLL a thousand times is probably not the fastest way to do it. But i'll give it a try.

@Xtra 's code is great, but it has machine code. He showed the sourcecode in his thread and I could convert this to my needs, but I have no clue how to convert that into machine code... or do you guys?

Greets Simon

User avatar
boiler
Posts: 16925
Joined: 21 Dec 2014, 02:44

Re: Image Filter everything but a range of similar colors

Post by boiler » 10 Aug 2022, 09:09

Xtra had used an online compiler that is no longer working, but there are other methods as described in this Mcode tutorial.

User avatar
Xtra
Posts: 2750
Joined: 02 Oct 2015, 12:15

Re: Image Filter everything but a range of similar colors

Post by Xtra » 10 Aug 2022, 10:24

Looping this function will not work. After the first call there is only 2 colors the TargetColor and ReplaceColor.
This function replaces all but the TargetColor

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Image Filter everything but a range of similar colors

Post by Spitzi » 10 Aug 2022, 13:56

boiler wrote:
10 Aug 2022, 09:09
Xtra had used an online compiler that is no longer working, but there are other methods as described in this Mcode tutorial.
Thanks. Interesting tutorial... This looks like an awful lot of crazily complicated work for a simple guy like me. Hmmm... I'll see what I can do

@iseahound: Do you think, your ImagePut-Library could be used for the job?

Greets Simon

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Image Filter everything but a range of similar colors

Post by iseahound » 10 Aug 2022, 17:40

Yes you would just do something like:

Code: Select all

pic := ImagePutBuffer("myimage.png")
pic.ColorKey(0xFFFF0000, 0x00000000) ; Repeat this as many times as you need. 
Just to be clear, the DllCall is the fastest part of the code.

User avatar
Xtra
Posts: 2750
Joined: 02 Oct 2015, 12:15

Re: Image Filter everything but a range of similar colors

Post by Xtra » 10 Aug 2022, 18:19

viewtopic.php?t=51100#p225689
Only the 64bit version posted in the linked post worked for me when i tested it last. But it does have variation option.
The 32bit version of Tics works.

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Image Filter everything but a range of similar colors

Post by iseahound » 22 Aug 2022, 10:05

@Xtra

Is this the source code? I feel like you want a working 32 and 64 bit version, just describe what the function does and I'll have something posted later today

Code: Select all

int Gdip_FilterColor(unsigned char * Bitmap, int w, int h, int Stride, unsigned int Color, unsigned int ReplaceColor, int v)
{
        unsigned int p, A1, R1, G1, B1, A2, R2, G2, B2, tA, tR, tG, tB;
 
        A1 = (Color & 0xff000000) >> 24;
        R1 = (Color & 0x00ff0000) >> 16;
        G1 = (Color & 0x0000ff00) >> 8;
        B1 = Color & 0x000000ff;
 
        A2 = (ReplaceColor & 0xff000000) >> 24;
        R2 = (ReplaceColor & 0x00ff0000) >> 16;
        G2 = (ReplaceColor & 0x0000ff00) >> 8;
        B2 = ReplaceColor & 0x000000ff;
 
        for (int y = 0; y < h; ++y)
        {
                for (int x = 0; x < w; ++x)
                {
                        p = (4*x)+(y*Stride);
 
                        tA = Bitmap[3+p];
                        tR = Bitmap[2+p];
                        tG = Bitmap[1+p];
                        tB = Bitmap[p];
                       
                        if ((tA <= A1+v && tA >= A1-v) && (tR <= R1+v && tR >= R1-v) && (tG <= G1+v && tG >= G1-v) && (tB <= B1+v && tB >= B1-v))
                        {
                                Bitmap[3+p] = A2;
                                Bitmap[2+p] = R2;
                                Bitmap[1+p] = G2;
                                Bitmap[p] = B2;
                        }
                       
                }
        }
        return 0;
}
Alright I added a new updated version into the old thread.
viewtopic.php?f=76&t=51100&p=478687#p478687

Works on 32 bit unicode and 64 bit Windows 11. I refactored the C code so it should perform much faster, as you can see by the length of the base64 code.

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Image Filter everything but a range of similar colors  Topic is solved

Post by Spitzi » 07 Sep 2022, 02:44

Hello @iseahound and @Xtra

Well, I finally solved my problem using your functions. Works like a charm and it is really really fast. Here's my code:

Code: Select all

#include Gdip_All_latest.ahk																						
#SingleInstance Force																								
#NoEnv  	


start := A_TickCount
FilterImage("C:\Temp\example5.png") 
duration := A_TickCount - start
MsgBox, Time:%duration%


start := A_TickCount
FilterImageFaster("C:\Temp\example4.png") 
duration := A_TickCount - start
MsgBox, Time:%duration%


return

^<::
Reload 
return


; Filters an image with grayscale stuff and colored text on it to become an image with just the text in white/black
; using standard GDIP functions and iterating through pixels
; every grey pixel (R=G=B ) becomes black
; every color pixel (nor R=G=B) becomes white
FilterImage(imagePath) {
	token := Gdip_Startup()
	
	pBitmap := Gdip_CreateBitmapFromFile(imagePath)
	Gdip_GetImageDimensions(pBitmap, Width, Height)
	
	x := 1
	while (x < Width) {
		y := 1
		while (y < Height) {
		
			ARGB := Gdip_GetPixel(pBitmap, x, y)
			Gdip_FromARGB(ARGB, A, R, G, B)

			if ((R==G) && (R==B)) {
				Gdip_SetPixel(pBitmap, x, y,"0xFF000000")
			} else {
				Gdip_SetPixel(pBitmap, x, y,"0xFFFFFFFF")
			}
			y += 1
			
		}
		x += 1
		; ToolTip, x:%x%
	}
	
	Gdip_Savebitmaptofile(pBitmap, "C:\Temp\filtered.png")
	
	Gdip_DisposeImage(pBitmap)
	Gdip_Shutdown(token)	
}




; same thing, but using the incredibly fast Functions with Mcode of iseahound and xtra
FilterImageFaster(imagePath) {
	token := Gdip_Startup()
	pBitmap := Gdip_CreateBitmapFromFile(imagePath)

	Gdip_FilterColor(pBitmap, 0xFFfcbf45, 0xFF00FF00, 50)		; everything I want is now green
	Gdip_ColorFilter(pBitmap, 0xFF00FF00, 0xFF000000)			; leave the green stuff, rest black
	Gdip_ColorFilter(pBitmap, 0xFF000000, 0xFFFFFFFF)			; green stuff is now white


	Gdip_Savebitmaptofile(pBitmap, "C:\Temp\filtered.png")
	
	Gdip_DisposeImage(pBitmap)
	Gdip_Shutdown(token)	
}








; Filters everything away except pixels of key-Color and variations of it, those are replaced by value
; Courtesy of iseahound
; https://www.autohotkey.com/boards/viewtopic.php?f=76&t=51100&p=478687#p478687
; -----------------------------------------------------------------------
Gdip_FilterColor(pBitmap, key, value, variation) {

   ; Get Bitmap width and height.
   DllCall("gdiplus\GdipGetImageWidth", "ptr", pBitmap, "uint*", width:=0)
   DllCall("gdiplus\GdipGetImageHeight", "ptr", pBitmap, "uint*", height:=0)

   ; Create a pixel buffer.
   VarSetCapacity(Rect, 16, 0)            ; sizeof(Rect) = 16
      NumPut(  width, Rect,  8,   "uint") ; Width
      NumPut( height, Rect, 12,   "uint") ; Height
   VarSetCapacity(BitmapData, 16+2*A_PtrSize, 0)   ; sizeof(BitmapData) = 24, 32
   DllCall("gdiplus\GdipBitmapLockBits"
            ,    "ptr", pBitmap
            ,    "ptr", &Rect
            ,   "uint", 3            ; ImageLockMode.ReadWrite
            ,    "int", 0x26200A     ; Format32bppArgb
            ,    "ptr", &BitmapData)
   Scan0 := NumGet(BitmapData, 16, "ptr")

   ; C source code - https://godbolt.org/z/3Gf341hsb
   static code := 0
   if !code {
      b64 := (A_PtrSize == 4)
         ? "VYnlVlNSikUQi3UIilUcik0gil0kiEX3ikUUiEX2ikUYiEX1O3UMcy2KRgI4RfdyIDpF9nIbikYBOEX1chM40HIPigY4wXIJONhyBYtFKIkGg8YE685YW15dww=="
         : "VlNEilQkOESKXCRAilwkSECKdCRQSInISDnQczGKSAJBOMhyI0Q4yXIeikgBQTjKchZEONlyEYoIOMtyC0A48XIGi0wkWIkISIPABOvKW17D"
      s64 := StrLen(RTrim(b64, "=")) * 3 // 4
      code := DllCall("GlobalAlloc", "uint", 0, "uptr", s64, "ptr")
      DllCall("crypt32\CryptStringToBinary", "str", b64, "uint", 0, "uint", 0x1, "ptr", code, "uint*", s64, "ptr", 0, "ptr", 0)
      DllCall("VirtualProtect", "ptr", code, "ptr", s64, "uint", 0x40, "uint*", op:=0)
   }

   v := abs(variation)
   r := ((key & 0xFF0000) >> 16)
   g := ((key & 0xFF00) >> 8)
   b := ((key & 0xFF))

   ; When doing pointer arithmetic, *Scan0 + 1 is actually adding 4 bytes.
   DllCall(code, "ptr", Scan0, "ptr", Scan0 + 4*width*height
            , "uchar", min(r+v, 255)
            , "uchar", max(r-v, 0)
            , "uchar", min(g+v, 255)
            , "uchar", max(g-v, 0)
            , "uchar", min(b+v, 255)
            , "uchar", max(b-v, 0)
            ,  "uint", value)

   ; Write pixels to bitmap.
   DllCall("gdiplus\GdipBitmapUnlockBits", "ptr", pBitmap, "ptr", &BitmapData)

   return pBitmap
}








; Filters everything away except pixels of TargetColor
; Courtesy of Xtra
; https://www.autohotkey.com/boards/viewtopic.php?style=1&t=98341#p436534
; -----------------------------------------------------------------------
Gdip_ColorFilter(pBitmap, TargetColor, ReplaceColor) 
{
	static ColorFilterMCode
	if (ColorFilterMCode = "")
	{
		if (A_PtrSize = 4)
			mCode := ""
			. "2,x86:VVdWU4PsBItUJCSLTCQoi1wkLItsJByNQgOF0g9JwotU"
			. "JCCByQAAAP/B+AKBywAAAP+F0n44he1+NIt0JBjB4ALB5QKJBC"
			. "Qx/420JgAAAACNFC6J8DsIdAKJGIPABDnQdfODxwEDNCQ5fCQg"
			. "deKDxAQxwFteX13D"
        else
            mCode := ""
			. "2,x64:VlNEi1QkQEGNWQNFhclBD0nZRItMJDhBgcoAAAD/wfsC"
			. "QYHJAAAA/0WFwH5QhdJ+TI1C/0hj20Ux20jB4wJIjTSFBAAAAG"
			. "YuDx+EAAAAAABIjRQOSInIZg8fhAAAAAAARDsIdANEiRBIg8AE"
			. "SDnQde9Bg8MBSAHZRTnYddMxwFtew5CQkJA="
		ColorFilterMCode := MCode(mCode)
	}

	Gdip_GetImageDimensions(pBitmap, w, h)
	Gdip_LockBits(pBitmap, 0, 0, w, h, stride, scan, bitmapData)
	DllCall(ColorFilterMCode, "uint", scan, "int", w, "int", h, "int", Stride, "uint", TargetColor, "uint", ReplaceColor, "cdecl")
	Gdip_UnlockBits(pBitmap, bitmapData)
}


MCode(mcode)
{
    static e := {1:4, 2:1}, c := (A_PtrSize=8) ? "x64" : "x86"
    if (!regexmatch(mcode, "^([0-9]+),(" c ":|.*?," c ":)([^,]+)", m))
        return
    if (!DllCall("crypt32\CryptStringToBinary", "str", m3, "uint", 0, "uint", e[m1], "ptr", 0, "uint*", s, "ptr", 0, "ptr", 0))
        return
    p := DllCall("GlobalAlloc", "uint", 0, "ptr", s, "ptr")
    if (c="x64")
        DllCall("VirtualProtect", "ptr", p, "ptr", s, "uint", 0x40, "uint*", op)
    if (DllCall("crypt32\CryptStringToBinary", "str", m3, "uint", 0, "uint", e[m1], "ptr", p, "uint*", s, "ptr", 0, "ptr", 0))
        return p
    DllCall("GlobalFree", "ptr", p)
}
Thanks a ton, you made my day. Spitzi

Post Reply

Return to “Ask for Help (v1)”