Code archives/Miscellaneous/Photoshop Like ColorPicker

This code has been declared by its author to be Public Domain code.

Download source code

Photoshop Like ColorPicker by Bobysait2016
Originally coded for a module to extend maxgui, this color picker is made to be easy to use, and can be used as an include.

You only need to call PickColor (with optional initial color argument) to launch a window in the style of photoshop color picker.

The color being modified is updated in the loop while the window is still on top.
It allows to modify the color of something without the need to hit "Ok" and try again with another color.
Also, if you hit "Cancel" or close the window, it will generate a last EVENT_COLOR event with the initial color as EventData(), so you can reset to the initial color the stuff that has been modified.

The module has not been tested in depth, so post comment if you find any kind of bug.

The Photoshop-like ColorPicker can convert RGB to HSL and back. So, it's pretty accurate for almost any case.
'Module mdt.uisdk

Import maxgui.drivers

Global EVENT_COLOR:Int			=	AllocHookId			( );

Type TColorPicker Extends TProxygadget
	
	Const _cursorS:Int = 12;
	
	Field _sourceEvent:Object;
	
	Field _initColor:Int;
	Field _color:Int;
	Field _rgb:Int[];
	Field _hsl:Int[];
	
	Field style:Int;
	Field _win:TGadget;
	Field _core:TGadget;
	
	Field _pix_sl:TPixmap;
	Field _pix_hue:TPixmap;
	
	Field _col_sl:TGadget;
	Field _col_hue:TGadget;
	Field _col_col:TGadget;
	Field _col_cH:TGadget;
	Field _col_tH:TGadget;
	Field _col_cS:TGadget;
	Field _col_tS:TGadget;
	Field _col_cL:TGadget;
	Field _col_tL:TGadget;
	Field _col_cR:TGadget;
	Field _col_tR:TGadget;
	Field _col_cG:TGadget;
	Field _col_tG:TGadget;
	Field _col_cB:TGadget;
	Field _col_tB:TGadget;
	Field _col_tHex:TGadget;
	
	Field _cursorHue:TGadget;
	
	Field _ok:TGadget;
	Field _cc:TGadget;
	
	Field _hueHit:Byte;
	Field _slHit:Byte;
	
	Function RGB2HSL:Int[](rgb:Int[])
		Local i0:Int = 0, i1:Int = 1, i2:Int = 2, t:Int = 0;
		If rgb[i1]>rgb[i0] Then t = i0; i0 = i1; i1=t;
		If rgb[i2]>rgb[i0] Then t = i0; i0 = i2; i2=t;
		If rgb[i2]>rgb[i1] Then t = i1; i1 = i2; i2=t;
		i1 = (i0+1)Mod(3); i2 = (i1+1)Mod(3);
		Local c:Int = rgb[i0] - rgb[i2]; If c=0 Then Return [0,0, (100*rgb[i0])/255];
		Return [(60*(rgb[i1]-rgb[i2]))/c+i0*120, (100*c)/rgb[i0], (100*rgb[i0])/255];
	End Function
	
	Function HSL2RGB :Int [](hsl :Int [])
		Local h:Float = Float(hsl[0])/60.0;
		Local s:Float = Float(hsl[1])/100.0;
		Local l:Float = Float(hsl[2])/100.0;
		Local r:Int, g:Int, b:Int, t:Float = h Mod(1.0);
		If h<1
			r = 255;
			g = 255*t;
			b = 0;
		ElseIf h<2
			r = 255-255*t;
			g = 255;
			b = 0;
		ElseIf h<3
			r = 0;
			g = 255;
			b = 255*t;
		ElseIf h<4
			r = 0;
			g = 255-255*t;
			b = 255;
		ElseIf h<5
			r = 255*t;
			g = 0;
			b = 255;
		Else
			r = 255
			g = 0
			b = 255-255*t;
		End If
		Return [Int ( (255+(r-255)*s)*l), Int ( (255+(g-255)*s)*l),Int ( (255+(b-255)*s)*l)  ];
	End Function
	
	Function rgbFromRGB:Int[](rgb:Int)
		Return [rgb Shr(16) & $FF, rgb Shr(8) & $FF, rgb & $FF];
	End Function
	
	
	Function getInstance:TColorPicker ( initRGBColor:Int=$FFFFFF, source:Object = Null, Style:Int = 0 )
		
		Local cp:TColorPicker		=	New TColorPicker;
		cp._sourceEvent				=	source;
		
		cp._initColor				=	initRGBColor;
		cp._rgb						=	rgbFromRGB(initRGBColor);
		cp._hsl						=	rgb2hsl(cp._rgb);
		
		cp.style					=	style;
		
		cp._win						=	CreateWindow		( "Color Picker" ..
																		, 0,0..
																		, 384, 278..
																		, Null..
																		, WINDOW_TITLEBAR|WINDOW_CLIENTCOORDS|..
																		  WINDOW_CENTER|WINDOW_HIDDEN..
															);
										
		cp._pix_sl					=	CreatePixmap		( 256, 256, PF_RGB888 );ClearPixels(cp._pix_sl, initRGBColor );
		cp._pix_hue					=	CreatePixmap		( 1, 360, PF_RGB888 );ClearPixels(cp._pix_hue, initRGBColor );
										
			cp._core				=	CreatePanel			( 0,0, ClientWidth(cp._win), ClientHeight(cp._win), cp._win);
										
			cp._col_hue				=	CreatePanel			( 276,010, 020, 256, cp._core, PANEL_ACTIVE );
										
			cp._col_sl				=	CreatePanel			( 010,010, 256, 256, cp._core, PANEL_ACTIVE );
										
			cp._col_col				=	CreatePanel			( 306,010, 065, 035, cp._core, PANEL_SUNKEN | PANEL_ACTIVE );
			
			cp._col_cH				=	CreateLabel			( "H", 306, 055, 030, 020, cp._core );
			cp._col_tH				=	CreateTextField		( 339, 055, 032, 020, cp._core );
										SetButtonState		( cp._col_cH, True );
			cp._col_cS				=	CreateLabel			( "S", 306, 077, 030, 020, cp._core );
			cp._col_tS				=	CreateTextField		( 339, 077, 032, 020, cp._core );
			cp._col_cL				=	CreateLabel			( "L", 306, 099, 030, 020, cp._core );
			cp._col_tL				=	CreateTextField		( 339, 099, 032, 020, cp._core );
										
			cp._col_cR				=	CreateLabel			( "R", 306, 124, 030, 020, cp._core );
			cp._col_tR				=	CreateTextField		( 339, 124, 032, 020, cp._core );
			cp._col_cG				=	CreateLabel			( "G", 306, 146, 030, 020, cp._core );
			cp._col_tG				=	CreateTextField		( 339, 146, 032, 020, cp._core );
			cp._col_cB				=	CreateLabel			( "B", 306, 168, 030, 020, cp._core );
			cp._col_tB				=	CreateTextField		( 339, 168, 032, 020, cp._core );
										
										CreateLabel			( "#", 306, 190, 015, 020, cp._core );
			cp._col_tHex			=	CreateTextField		( 321, 190, 050, 020, cp._core );
										
			cp._ok					=	CreateButton		( "Ok", 306, 214, 065, 020, cp._core, BUTTON_OK );
			cp._cc					=	CreateButton		( "Cancel", 306, 236, 065, 020, cp._core, BUTTON_CANCEL );
										
			cp._cursorHue			=	CreatePanel			( 271,010, 030,001, cp._core );
										SetPanelColor		( cp._cursorHue, 1,1,1 );
			
		cp.SetProxy(cp._win);
		AddHook							( EmitEventHook, EventHook, cp );
		
		cp								.SetCurrentColor	( initRGBColor );
			
			cp							._updateHuePix		( );
			cp							._updateSLPix		( );
			cp							._updateUI			( );
			
			SetGadgetPixmap				( cp._col_hue , cp._pix_hue, PANELPIXMAP_STRETCH );
			SetGadgetPixmap				( cp._col_sl , cp._pix_sl );
		ShowGadget						( cp._win );
		RedrawGadget					( cp._win );
		
		Return cp;
		
	End Function
	
	Function clampI(i:Int var, m0:Int, m1:Int)
		i=max(min(i, m1), m0);
	End Function
	
	Method pickHueColor(r:Int Var, g:Int Var, b:Int Var )
		Local rgb:Int = Self._pix_hue.ReadPixel(0,359-Self._hsl[0]);
		r = rgb Shr(16) & $FF;
		g = rgb Shr(8) & $FF;
		b = rgb & $FF;
		Return;
	End Method
	
	' update ui gadgets
	Method _updateUI ( )
		' update color
		Self._color		=	(Self._rgb[0] Shl(16)) + (Self._rgb[1] Shl(8)) + Self._rgb[2];
		' textfields
		Self._col_tHex		.SetText			( Right(Hex(Self._color),6) );
		Self._col_tH		.SetText			( Self._hsl[0] );
		Self._col_tS		.SetText			( Self._hsl[1] );
		Self._col_tL		.SetText			( Self._hsl[2] );
		Self._col_tR		.SetText			( Self._rgb[0] );
		Self._col_tG		.SetText			( Self._rgb[1] );
		Self._col_tB		.SetText			( Self._rgb[2] );
		' hue cursor
		SetGadgetShape		( Self._cursorHue, 271, 10+255-Self._hsl[0]*255/359, 30, 1);
		' refresh color panel
		SetPanelColor		( Self._col_col, Self._rgb[0], Self._rgb[1], Self._rgb[2] );
		
		RedrawGadget		( Self._win );
		
		' emit the event for the main program to receive the modification in realtime.
		EmitEvent			( CreateEvent(EVENT_COLOR, Self._sourceEvent, Self._color) );
		
	End Method
	
	Method SetCurrentColor(RGB:Int)
		Self._rgb		=	rgbFromRGB			( RGB );
		Self._hsl		=	rgb2hsl				( Self._rgb );
							clampI				( Self._hsl[0], 0,359 );
							clampI				( Self._hsl[1], 0,99 );
							clampI				( Self._hsl[2], 0,99 );
		Self				._updateHuePix		( );
		Self				._updateSLPix		( );
		Self				._updateUI			( );
	End Method
	
	
	Method _updateHuePix();
		Local r:Int, g:Int, b:Int, u0:Int, u1:Int;
		Local du1:Int = 60;
		Local du2:Int = 60;
		
		Local x:Int = 0;
		
		u1 = 0;
		
		u0 = u1; u1 = u0+du1;
		' R(255) - B(0->255)
		For Local y0:Int = u0 Until u1
			r = 255; g = 0; b = 255*(y0-u0)/(u1-u0);
			Self._pix_hue.WritePixel(x,y0, r Shl(16) | g Shl(8) | b);
		Next;
		
		u0 = u1; u1 = u0+du2;
		' R(255->0) - B(255)
		For Local y1:Int = u0 Until u1
			r = 255-255*(y1-u0)/(u1-u0); g = 0; b = 255;
			Self._pix_hue.WritePixel(x,y1, r Shl(16) | g Shl(8) | b);
		Next;
		
		u0 = u1; u1 = u0+du1;
		' B(255) - G(0->255)
		For Local y2:Int = u0 Until u1
			r = 0; g = 255*(y2-u0)/(u1-u0); b = 255;
			Self._pix_hue.WritePixel(x,y2, r Shl(16) | g Shl(8) | b);
		Next
		
		u0 = u1; u1 = u0+du2;
		' B(255->0) - G(255)
		For Local y3:Int = u0 Until u1
			r = 0; g = 255; b = 255-255*(y3-u0)/(u1-u0);
			Self._pix_hue.WritePixel(x,y3, r Shl(16) | g Shl(8) | b);
		Next;
		
		u0 = u1; u1 = u0+du1;
		' G(255) - R(0->255)
		For Local y4:Int = u0 Until u1
			r = 255*(y4-u0)/(u1-u0); g = 255; b = 0;
			Self._pix_hue.WritePixel(x,y4, r Shl(16) | g Shl(8) | b);
		Next;
		
		u0 = u1; u1 = u0+du2;
		' G(255->0) - R(255)
		For Local y5:Int = u0 Until u1
			r = 255; g = 255-255*(y5-u0)/(u1-u0); b = 0;
			Self._pix_hue.WritePixel(x,y5, r Shl(16) | g Shl(8) | b);
		Next;
		
		SetGadgetPixmap	( Self._col_hue, Self._pix_hue, PANELPIXMAP_STRETCH );
	End Method
	
	Method _updateSLPix()
		
		Local r:Int=0, g:Int=0, b:Int=0;
		Self.pickHueColor(r,g,b);
		
		For Local y:Int = 0 To 255
			Local rnb:Float = 1.0-Float(y) / 255;
			For Local x:Int = 0 To 255
				Local whyte:Float = Float(x)/255;
				Local r_:Int = (255+(r-255)*whyte)*rnb;
				Local g_:Int = (255+(g-255)*whyte)*rnb;
				Local b_:Int = (255+(b-255)*whyte)*rnb;
				Self._pix_sl.WritePixel(x,y, r_ Shl(16) | g_ Shl(8) | b_);
			Next;
		Next;
		
		Self._updateSLCursor();
		
		SetGadgetPixmap		( Self._col_sl , Self._pix_sl );
		
	End Method
	
	Method _updateSLCursor()
		
		' draw the new cursor
		Local cr0:Int = Floor(Float(_cursorS)*.5);
		Local perimeter:Int = cr0 * 2 * Pi;
		Local cr1:Int = cr0-1;
		Local sat:Int = (255 * Self._hsl[1])/100;
		Local lit:Int = (255 * (100-Self._hsl[2]))/100;
		For Local p:Int = 0 To perimeter
			Local ang:Float = Float(p*360)/perimeter;
			Local x0:Int = sat + Cos(ang)*cr0;
			Local y0:Int = lit + Sin(ang)*cr0;
			Local x1:Int = sat + Cos(ang)*cr1;
			Local y1:Int = lit + Sin(ang)*cr1;
			If x0>=0 And x0<256
				If y0>=0 And y0<256 Then Self._pix_sl.WritePixel(x0,y0, $FF010101);
			EndIf;
			If x1>=0 And x1<256
				If y1>=0 And y1<256 Then Self._pix_sl.WritePixel(x1,y1, $FFFFFFFF);
			EndIf;
		Next;
		
		SetGadgetPixmap		( Self._col_sl , Self._pix_sl );
		
	End Method
	
	
	Method clearSLCursor()
		
		' overwrite old cursor
		Local r:Int=Self._rgb[0], g:Int=Self._rgb[1], b:Int=Self._rgb[2];
		Self.pickHueColor(r,g,b);
		Local sat:Int = 255*Float(Self._hsl[1])/99;
		Local lit:Int = 255*Float(99-Self._hsl[2])/99;
		For Local y:Int = -_cursorS-1 To _cursorS+1
			Local j:Int = lit+y;
			If j>=0 And j<256
				Local rnb:Float = 1.0-Float(j) / 255;
				For Local x:Int = -_cursorS-1 To _cursorS+1
					Local i:Int = sat+x;
					If i>=0 And i<256
						Local whyte:Float = Float(i)/255;
						Local r_:Int = (255+(r-255)*whyte)*rnb;
						Local g_:Int = (255+(g-255)*whyte)*rnb;
						Local b_:Int = (255+(b-255)*whyte)*rnb;
						Self._pix_sl.WritePixel(i,j, r_ Shl(16) | g_ Shl(8) | b_);
					End If;
				Next;
			End If;
		Next;
		
	End Method
	
	Method setHue ( h:Int )
		
		' clamp hue
		clampI(h, 0,359);
		
		' apply only if different from current pos
		If Self._hsl[0]<>h
			
			Self._hsl[0]	=	h;
			
			' update current color
			Self._rgb		=	hsl2rgb(Self._hsl);
			
			' update gadgets
			Self				._updateSLPix	( );
			Self				._updateUI		( );
			
		EndIf;
		
	End Method
	
	Method setSL ( s:Int, l:Int )
		
		clampI(s, 0,100);
		clampI(l, 0,100);
		
		If ( (s<>Self._hsl[1]) Or (l<> Self._hsl[2]) )
			Self				.clearSLCursor	( )
			Self._hsl[1]	=	s;
			Self._hsl[2]	=	l;
			Self._rgb		=	hsl2rgb(Self._hsl);
			Self				._updateSLCursor( );
			Self				._updateUI		( );
		EndIf;
		
	End Method
	
	Method setSat(s:Int)
		clampI(s, 0,100);
		If (Self._hsl[1]<>s)
			Self				.clearSLCursor	( );
			Self._hsl[1]	=	s;
			Self._rgb		=	HSL2RGB(Self._hsl);
			Self				._updateSLCursor( );
			Self._updateUI ();
		EndIf;
	End Method
	
	Method setLight(l:Int)
		clampI(l, 0, 100);
		If Self._hsl[2]<>l
			Self				.clearSLCursor	( );
			Self._hsl[2]	=	l;
			Self._rgb		=	HSL2RGB(Self._hsl);
			Self				._updateSLCursor( );
			Self._updateUI ();
		EndIf;
	End Method
	
	Method setRed(r:Int)
		clampI(r, 0, 255);
		If Self._rgb[0]<>r
			Self._rgb[0]	=	r;
			Self._hsl		=	rgb2hsl(Self._rgb);
			Self._updateUI ();
		EndIf;
	End Method
	
	Method setGreen(g:Int)
		clampI(g, 0, 255);
		If Self._rgb[1]<>g
			Self._rgb[1]	=	g;
			Self._hsl		=	rgb2hsl(Self._rgb);
			Self._updateUI();
		EndIf;
	End Method
	
	Method setBlue(b:Int)
		clampI(b, 0, 255);
		If Self._rgb[2]<>b
			Self._rgb[2]	=	b;
			Self._hsl		=	RGB2HSL(Self._rgb);
			Self._updateUI();
		EndIf;
	End Method
	
	
	Method ProcessEvent:Int(event:TEvent)
		Select event.id
			
			Case EVENT_WINDOWCLOSE
				
				If event.Source=Self
					FreeGadget Self;
					Return True;
				EndIf;
				
			Case EVENT_MOUSEMOVE
				
				Select event.source
					
					' mouse down on hue pixmap
					Case Self._col_hue
						
						If Self._hueHit Then Self.setHue(359-Float(359*event.y)/255);
						Self._slHit = False;
						Return True;
						
					' mouse down on saturation/brightness pixmap
					Case Self._col_sl
						
						If Self._slHit Then Self.setsl(100*Float(event.x)/255.0, 100-100*Float(event.y)/255.0);
						Self._hueHit = False;
						Return True;
						
					Default
						
						Self._slHit = False;
						Self._hueHit = False;
						Return False;
						
				End Select
				
			Case EVENT_MOUSEUP
				
				Self._slHit = False;
				Self._hueHit = False;
				Return False;
				
			Case EVENT_MOUSEDOWN
				
				Select event.source
					
					Case Self._col_hue
						
						Self.setHue(359-Float(359*event.y)/255);
						
						Self._slHit = False;
						Self._hueHit = True;
						Return True;
						
					Case Self._col_sl
						
						Self.setSL(100*Float(event.x)/255.0, 100-100*Float(event.y)/255.0);
						
						Self._slHit = True;
						Self._hueHit = False;
						Return True;
						
				End Select
				
			'Case EVENT_MOUSEENTER
				
			'Case EVENT_MOUSELEAVE
				
			Case EVENT_GADGETACTION
				
				Select event.Source
					
					Case Self._col_tHex
						
						Self.SetCurrentColor ( ("$"+Self._col_tHex.GetText()).ToInt() ); Return True;
						
					Case Self._col_tH
						
						Self.setHue(Self._col_tH.GetText().ToInt()); Return True;
						
					Case Self._col_tS
						
						Self.setSat(Self._col_tS.GetText().ToInt()); Return True;
					
					Case Self._col_tL
						
						Self.setLight(Self._col_tL.GetText().ToInt()); Return True;
						
					Case Self._col_tR
						
						Self.setRed(Self._col_tR.GetText().ToInt()); Return True;
					
					Case Self._col_tG
						
						Self.setGreen(Self._col_tG.GetText().ToInt()); Return True;
						
					Case Self._col_tB
						
						Self.setBlue(Self._col_tB.GetText().ToInt()); Return True;
						
					Case Self._ok
						
						FreeGadget Self;
						Return True;
						
					' cancel
					Case Self._cc
						
						' reset color to its initial value
						EmitEvent CreateEvent ( EVENT_COLOR, Self._sourceEvent, Self._initColor );
						FreeGadget Self;
						Return True;
						
				End Select;
				
		End Select;
		
		Return False;
		
	EndMethod
	
	
	
	Function EventHook:Object(id:Int,data:Object,context:Object)
		
		If TEvent(data)<>Null Then If TColorPicker(context)<>Null Then If TColorPicker(context).ProcessEvent(TEvent(data)) Then Return Null;
		Return data;
		
	EndFunction
	
	Method CleanUp()
		_sourceEvent=Null;
		_rgb =Null;
		_hsl=Null;
		_col_col = Null; _pix_sl = Null; _col_tHex = Null;
		_pix_hue = Null; _col_sl = Null; _col_hue = Null;
		_col_cH = Null; _col_tH = Null;
		_col_cS = Null; _col_tS = Null;
		_col_cL = Null; _col_tL = Null;
		_col_cR = Null; _col_tR = Null;
		_col_cG = Null; _col_tG = Null;
		_col_cB = Null; _col_tB = Null;
		_cursorHue=Null;
		_ok=Null;
		_cc=Null;
		If _core<>Null Then FreeGadget(_core); _core=Null;
		If _win<>Null Then FreeGadget(_win); _win=Null;
		RemoveHook(EmitEventHook, EventHook, Self);
		Super.CleanUp();
	End Method
	
	
	Function Demo()
		
		Local WIN:TGadget = CreateWindow("test", 0,0,600,300,Null, WINDOW_TITLEBAR|WINDOW_CENTER|WINDOW_RESIZABLE)
		Local button:TGadget = CreateButton("pick", 2,2,ClientWidth(WIN)-4,ClientHeight(WIN)-4,WIN)
		SetGadgetLayout(button, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED);
		
		Local rgb:Int = $FF8000
		
		Repeat
			While PollEvent()<>Null
				Select EventID()
					Case EVENT_WINDOWCLOSE
						End
						
					Case EVENT_GADGETACTION
						If EventSource() = button Then pickColor(rgb)
						
					Case EVENT_COLOR
						rgb = EventData(); SetGadgetColor button, rgb Shr(16) & $ff, rgb Shr(8) & $ff, rgb & $ff, 1
						
				End Select
			Wend;
			
			Delay 30;
			
		Forever
		
	End Function

End Type

Function pickColor ( initColor:Int=$FFFFFF, source:Object=Null, Style:Int = 0 )
	TColorPicker.getInstance ( initColor, source, Style);
End Function



TColorPicker.Demo(); End;

Comments

None.

Code Archives Forum