1 // Copyright (c) 2005, Niels Martin Hansen
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 //   * Redistributions of source code must retain the above copyright notice,
8 //     this list of conditions and the following disclaimer.
9 //   * Redistributions in binary form must reproduce the above copyright notice,
10 //     this list of conditions and the following disclaimer in the documentation
11 //     and/or other materials provided with the distribution.
12 //   * Neither the name of the Aegisub Group nor the names of its contributors
13 //     may be used to endorse or promote products derived from this software
14 //     without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 // POSSIBILITY OF SUCH DAMAGE.
27 //
28 // Aegisub Project http://www.aegisub.org/
29 
30 #include "colorspace.h"
31 #include "compat.h"
32 #include "help_button.h"
33 #include "libresrc/libresrc.h"
34 #include "options.h"
35 #include "persist_location.h"
36 #include "utils.h"
37 
38 #include <libaegisub/scoped_ptr.h>
39 #include <libaegisub/make_unique.h>
40 
41 #include <memory>
42 #include <vector>
43 
44 #include <wx/bitmap.h>
45 #include <wx/button.h>
46 #include <wx/choice.h>
47 #include <wx/dcclient.h>
48 #include <wx/dcmemory.h>
49 #include <wx/dcscreen.h>
50 #include <wx/dialog.h>
51 #include <wx/event.h>
52 #include <wx/image.h>
53 #include <wx/rawbmp.h>
54 #include <wx/settings.h>
55 #include <wx/sizer.h>
56 #include <wx/spinctrl.h>
57 #include <wx/statbmp.h>
58 #include <wx/statbox.h>
59 #include <wx/stattext.h>
60 #include <wx/textctrl.h>
61 
62 #ifdef __WXMAC__
63 #include <ApplicationServices/ApplicationServices.h>
64 #endif
65 
66 namespace {
67 
68 enum class PickerDirection {
69 	HorzVert,
70 	Horz,
71 	Vert
72 };
73 
74 static const int spectrum_horz_vert_arrow_size = 4;
75 
76 wxDEFINE_EVENT(EVT_SPECTRUM_CHANGE, wxCommandEvent);
77 
78 class ColorPickerSpectrum final : public wxControl {
79 	int x;
80 	int y;
81 
82 	wxBitmap *background;
83 	PickerDirection direction;
84 
OnPaint(wxPaintEvent & evt)85 	void OnPaint(wxPaintEvent &evt) {
86 		if (!background) return;
87 
88 		int height = background->GetHeight();
89 		int width = background->GetWidth();
90 		wxPaintDC dc(this);
91 
92 		wxMemoryDC memdc;
93 		memdc.SelectObject(*background);
94 		dc.Blit(1, 1, width, height, &memdc, 0, 0);
95 
96 		wxPoint arrow[3];
97 		wxRect arrow_box;
98 
99 		wxPen invpen(*wxWHITE, 3);
100 		invpen.SetCap(wxCAP_BUTT);
101 		dc.SetLogicalFunction(wxXOR);
102 		dc.SetPen(invpen);
103 
104 		switch (direction) {
105 			case PickerDirection::HorzVert:
106 				// Make a little cross
107 				dc.DrawLine(x-4, y+1, x+7, y+1);
108 				dc.DrawLine(x+1, y-4, x+1, y+7);
109 				break;
110 			case PickerDirection::Horz:
111 				// Make a vertical line stretching all the way across
112 				dc.DrawLine(x+1, 1, x+1, height+1);
113 				// Points for arrow
114 				arrow[0] = wxPoint(x+1, height+2);
115 				arrow[1] = wxPoint(x+1-spectrum_horz_vert_arrow_size, height+2+spectrum_horz_vert_arrow_size);
116 				arrow[2] = wxPoint(x+1+spectrum_horz_vert_arrow_size, height+2+spectrum_horz_vert_arrow_size);
117 
118 				arrow_box.SetLeft(0);
119 				arrow_box.SetTop(height + 2);
120 				arrow_box.SetRight(width + 1 + spectrum_horz_vert_arrow_size);
121 				arrow_box.SetBottom(height + 2 + spectrum_horz_vert_arrow_size);
122 				break;
123 			case PickerDirection::Vert:
124 				// Make a horizontal line stretching all the way across
125 				dc.DrawLine(1, y+1, width+1, y+1);
126 				// Points for arrow
127 				arrow[0] = wxPoint(width+2, y+1);
128 				arrow[1] = wxPoint(width+2+spectrum_horz_vert_arrow_size, y+1-spectrum_horz_vert_arrow_size);
129 				arrow[2] = wxPoint(width+2+spectrum_horz_vert_arrow_size, y+1+spectrum_horz_vert_arrow_size);
130 
131 				arrow_box.SetLeft(width + 2);
132 				arrow_box.SetTop(0);
133 				arrow_box.SetRight(width + 2 + spectrum_horz_vert_arrow_size);
134 				arrow_box.SetBottom(height + 1 + spectrum_horz_vert_arrow_size);
135 				break;
136 		}
137 
138 		if (direction == PickerDirection::Horz || direction == PickerDirection::Vert) {
139 			wxBrush bgBrush;
140 			bgBrush.SetColour(GetBackgroundColour());
141 			dc.SetLogicalFunction(wxCOPY);
142 			dc.SetPen(*wxTRANSPARENT_PEN);
143 			dc.SetBrush(bgBrush);
144 			dc.DrawRectangle(arrow_box);
145 
146 			// Arrow pointing at current point
147 			dc.SetBrush(*wxBLACK_BRUSH);
148 			dc.DrawPolygon(3, arrow);
149 		}
150 
151 		// Border around the spectrum
152 		wxPen blkpen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), 1);
153 		blkpen.SetCap(wxCAP_BUTT);
154 
155 		dc.SetLogicalFunction(wxCOPY);
156 		dc.SetPen(blkpen);
157 		dc.SetBrush(*wxTRANSPARENT_BRUSH);
158 		dc.DrawRectangle(0, 0, background->GetWidth()+2, background->GetHeight()+2);
159 	}
160 
OnMouse(wxMouseEvent & evt)161 	void OnMouse(wxMouseEvent &evt) {
162 		evt.Skip();
163 
164 		// We only care about mouse move events during a drag
165 		if (evt.Moving())
166 			return;
167 
168 		if (evt.LeftDown()) {
169 			CaptureMouse();
170 			SetCursor(wxCursor(wxCURSOR_BLANK));
171 		}
172 		else if (evt.LeftUp() && HasCapture()) {
173 			ReleaseMouse();
174 			SetCursor(wxNullCursor);
175 		}
176 
177 		if (evt.LeftDown() || (HasCapture() && evt.LeftIsDown())) {
178 			// Adjust for the 1px black border around the control
179 			int newx = mid(0, evt.GetX() - 1, GetClientSize().x - 3);
180 			int newy = mid(0, evt.GetY() - 1, GetClientSize().y - 3);
181 			SetXY(newx, newy);
182 			wxCommandEvent evt2(EVT_SPECTRUM_CHANGE, GetId());
183 			AddPendingEvent(evt2);
184 		}
185 	}
186 
AcceptsFocusFromKeyboard() const187 	bool AcceptsFocusFromKeyboard() const override { return false; }
188 
189 public:
ColorPickerSpectrum(wxWindow * parent,PickerDirection direction,wxSize size)190 	ColorPickerSpectrum(wxWindow *parent, PickerDirection direction, wxSize size)
191 	: wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
192 	, x(-1)
193 	, y(-1)
194 	, background(nullptr)
195 	, direction(direction)
196 	{
197 		size.x += 2;
198 		size.y += 2;
199 
200 		if (direction == PickerDirection::Vert) size.x += spectrum_horz_vert_arrow_size + 1;
201 		if (direction == PickerDirection::Horz) size.y += spectrum_horz_vert_arrow_size + 1;
202 
203 		SetClientSize(size);
204 		SetMinSize(GetSize());
205 
206 		Bind(wxEVT_LEFT_DOWN, &ColorPickerSpectrum::OnMouse, this);
207 		Bind(wxEVT_LEFT_UP, &ColorPickerSpectrum::OnMouse, this);
208 		Bind(wxEVT_MOTION, &ColorPickerSpectrum::OnMouse, this);
209 		Bind(wxEVT_PAINT, &ColorPickerSpectrum::OnPaint, this);
210 	}
211 
GetX() const212 	int GetX() const { return x; }
GetY() const213 	int GetY() const { return y; }
214 
SetXY(int xx,int yy)215 	void SetXY(int xx, int yy) {
216 		if (x != xx || y != yy) {
217 			x = xx;
218 			y = yy;
219 			Refresh(false);
220 		}
221 	}
222 
223 	/// @brief Set the background image for this spectrum
224 	/// @param new_background New background image
225 	/// @param force Repaint even if it appears to be the same image
SetBackground(wxBitmap * new_background,bool force=false)226 	void SetBackground(wxBitmap *new_background, bool force = false) {
227 		if (background == new_background && !force) return;
228 		background = new_background;
229 		Refresh(false);
230 	}
231 };
232 
233 #ifdef WIN32
234 #define STATIC_BORDER_FLAG wxSTATIC_BORDER
235 #else
236 #define STATIC_BORDER_FLAG wxSIMPLE_BORDER
237 #endif
238 
239 wxDEFINE_EVENT(EVT_RECENT_SELECT, wxThreadEvent);
240 
241 /// @class ColorPickerRecent
242 /// @brief A grid of recently used colors which can be selected by clicking on them
243 class ColorPickerRecent final : public wxStaticBitmap {
244 	int rows;     ///< Number of rows of colors
245 	int cols;     ///< Number of cols of colors
246 	int cellsize; ///< Width/Height of each cell
247 
248 	/// The colors currently displayed in the control
249 	std::vector<agi::Color> colors;
250 
OnClick(wxMouseEvent & evt)251 	void OnClick(wxMouseEvent &evt) {
252 		wxSize cs = GetClientSize();
253 		int cx = evt.GetX() * cols / cs.x;
254 		int cy = evt.GetY() * rows / cs.y;
255 		if (cx < 0 || cx > cols || cy < 0 || cy > rows) return;
256 		int i = cols*cy + cx;
257 
258 		if (i >= 0 && i < (int)colors.size()) {
259 			wxThreadEvent evnt(EVT_RECENT_SELECT, GetId());
260 			evnt.SetPayload(colors[i]);
261 			AddPendingEvent(evnt);
262 		}
263 	}
264 
UpdateBitmap()265 	void UpdateBitmap() {
266 		wxSize sz = GetClientSize();
267 
268 		wxBitmap background(sz.x, sz.y);
269 		wxMemoryDC dc(background);
270 
271 		dc.SetPen(*wxTRANSPARENT_PEN);
272 
273 		for (int cy = 0; cy < rows; cy++) {
274 			for (int cx = 0; cx < cols; cx++) {
275 				int x = cx * cellsize;
276 				int y = cy * cellsize;
277 
278 				dc.SetBrush(wxBrush(to_wx(colors[cy * cols + cx])));
279 				dc.DrawRectangle(x, y, x+cellsize, y+cellsize);
280 			}
281 		}
282 
283 		{
284 			wxEventBlocker blocker(this);
285 			SetBitmap(background);
286 		}
287 
288 		Refresh(false);
289 	}
290 
AcceptsFocusFromKeyboard() const291 	bool AcceptsFocusFromKeyboard() const override { return false; }
292 
293 public:
ColorPickerRecent(wxWindow * parent,int cols,int rows,int cellsize)294 	ColorPickerRecent(wxWindow *parent, int cols, int rows, int cellsize)
295 	: wxStaticBitmap(parent, -1, wxBitmap(), wxDefaultPosition, wxDefaultSize, STATIC_BORDER_FLAG)
296 	, rows(rows)
297 	, cols(cols)
298 	, cellsize(cellsize)
299 	{
300 		colors.resize(rows * cols);
301 		SetClientSize(cols*cellsize, rows*cellsize);
302 		SetMinSize(GetSize());
303 		SetMaxSize(GetSize());
304 		SetCursor(*wxCROSS_CURSOR);
305 
306 		Bind(wxEVT_LEFT_DOWN, &ColorPickerRecent::OnClick, this);
307 		Bind(wxEVT_SIZE, [=](wxSizeEvent&) { UpdateBitmap(); });
308 	}
309 
310 	/// Load the colors to show
Load(std::vector<agi::Color> const & recent_colors)311 	void Load(std::vector<agi::Color> const& recent_colors) {
312 		colors = recent_colors;
313 		colors.resize(rows * cols);
314 		UpdateBitmap();
315 	}
316 
317 	/// Get the list of recent colors
Save() const318 	std::vector<agi::Color> Save() const { return colors; }
319 
320 	/// Add a color to the beginning of the recent list
AddColor(agi::Color color)321 	void AddColor(agi::Color color) {
322 		auto existing = find(colors.begin(), colors.end(), color);
323 		if (existing != colors.end())
324 			rotate(colors.begin(), existing, existing + 1);
325 		else {
326 			colors.insert(colors.begin(), color);
327 			colors.pop_back();
328 		}
329 
330 		UpdateBitmap();
331 	}
332 };
333 
334 wxDEFINE_EVENT(EVT_DROPPER_SELECT, wxThreadEvent);
335 
336 class ColorPickerScreenDropper final : public wxControl {
337 	wxBitmap capture;
338 
339 	int resx, resy;
340 	int magnification;
341 
OnMouse(wxMouseEvent & evt)342 	void OnMouse(wxMouseEvent &evt) {
343 		int x = evt.GetX();
344 		int y = evt.GetY();
345 
346 		if (x >= 0 && x < capture.GetWidth() && y >= 0 && y < capture.GetHeight()) {
347 			wxNativePixelData pd(capture, wxRect(x, y, 1, 1));
348 			wxNativePixelData::Iterator pdi(pd.GetPixels());
349 			agi::Color color(pdi.Red(), pdi.Green(), pdi.Blue(), 0);
350 
351 			wxThreadEvent evnt(EVT_DROPPER_SELECT, GetId());
352 			evnt.SetPayload(color);
353 			AddPendingEvent(evnt);
354 		}
355 	}
356 
OnPaint(wxPaintEvent & evt)357 	void OnPaint(wxPaintEvent &evt) {
358 		wxPaintDC(this).DrawBitmap(capture, 0, 0);
359 	}
360 
AcceptsFocusFromKeyboard() const361 	bool AcceptsFocusFromKeyboard() const override { return false; }
362 
363 public:
ColorPickerScreenDropper(wxWindow * parent,int resx,int resy,int magnification)364 	ColorPickerScreenDropper(wxWindow *parent, int resx, int resy, int magnification)
365 	: wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, STATIC_BORDER_FLAG)
366 	, capture(resx * magnification, resy * magnification, wxNativePixelFormat::BitsPerPixel)
367 	, resx(resx)
368 	, resy(resy)
369 	, magnification(magnification)
370 	{
371 		SetClientSize(resx*magnification, resy*magnification);
372 		SetMinSize(GetSize());
373 		SetMaxSize(GetSize());
374 		SetCursor(*wxCROSS_CURSOR);
375 
376 		wxMemoryDC capdc(capture);
377 		capdc.SetPen(*wxTRANSPARENT_PEN);
378 		capdc.SetBrush(*wxWHITE_BRUSH);
379 		capdc.DrawRectangle(0, 0, capture.GetWidth(), capture.GetHeight());
380 
381 		Bind(wxEVT_PAINT, &ColorPickerScreenDropper::OnPaint, this);
382 		Bind(wxEVT_LEFT_DOWN, &ColorPickerScreenDropper::OnMouse, this);
383 	}
384 
385 	void DropFromScreenXY(int x, int y);
386 };
387 
DropFromScreenXY(int x,int y)388 void ColorPickerScreenDropper::DropFromScreenXY(int x, int y) {
389 	wxMemoryDC capdc(capture);
390 	capdc.SetPen(*wxTRANSPARENT_PEN);
391 #ifndef __WXMAC__
392 	wxScreenDC screen;
393 	capdc.StretchBlit(0, 0, resx * magnification, resy * magnification,
394 		&screen, x - resx / 2, y - resy / 2, resx, resy);
395 #else
396 	// wxScreenDC doesn't work on recent versions of OS X so do it manually
397 
398 	// Doesn't bother handling the case where the rect overlaps two monitors
399 	CGDirectDisplayID display_id;
400 	uint32_t display_count;
401 	CGGetDisplaysWithPoint(CGPointMake(x, y), 1, &display_id, &display_count);
402 
403 	agi::scoped_holder<CGImageRef> img(CGDisplayCreateImageForRect(display_id, CGRectMake(x - resx / 2, y - resy / 2, resx, resy)), CGImageRelease);
404 	NSUInteger width = CGImageGetWidth(img);
405 	NSUInteger height = CGImageGetHeight(img);
406 	std::vector<uint8_t> imgdata(height * width * 4);
407 
408 	agi::scoped_holder<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB(), CGColorSpaceRelease);
409 	agi::scoped_holder<CGContextRef> bmp_context(CGBitmapContextCreate(&imgdata[0], width, height, 8, 4 * width, colorspace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big), CGContextRelease);
410 
411 	CGContextDrawImage(bmp_context, CGRectMake(0, 0, width, height), img);
412 
413 	for (int x = 0; x < resx; x++) {
414 		for (int y = 0; y < resy; y++) {
415 			uint8_t *pixel = &imgdata[y * width * 4 + x * 4];
416 			capdc.SetBrush(wxBrush(wxColour(pixel[0], pixel[1], pixel[2])));
417 			capdc.DrawRectangle(x * magnification, y * magnification, magnification, magnification);
418 		}
419 	}
420 #endif
421 
422 	Refresh(false);
423 }
424 
425 class DialogColorPicker final : public wxDialog {
426 	std::unique_ptr<PersistLocation> persist;
427 
428 	agi::Color cur_color; ///< Currently selected colour
429 
430 	bool spectrum_dirty; ///< Does the spectrum image need to be regenerated?
431 	ColorPickerSpectrum *spectrum; ///< The 2D color spectrum
432 	ColorPickerSpectrum *slider; ///< The 1D slider for the color component not in the slider
433 	ColorPickerSpectrum *alpha_slider;
434 
435 	wxChoice *colorspace_choice; ///< The dropdown list to select colorspaces
436 
437 	wxSpinCtrl *rgb_input[3];
438 	wxBitmap rgb_spectrum[3]; ///< x/y spectrum bitmap where color "i" is excluded from
439 	wxBitmap rgb_slider[3];   ///< z spectrum for color "i"
440 
441 	wxSpinCtrl *hsl_input[3];
442 	wxBitmap hsl_spectrum; ///< h/s spectrum
443 	wxBitmap hsl_slider;   ///< l spectrum
444 
445 	wxSpinCtrl *hsv_input[3];
446 	wxBitmap hsv_spectrum; ///< s/v spectrum
447 	wxBitmap hsv_slider;   ///< h spectrum
448 	wxBitmap alpha_slider_img;
449 
450 	wxTextCtrl *ass_input;
451 	wxTextCtrl *html_input;
452 	wxSpinCtrl *alpha_input;
453 
454 	/// The eyedropper is set to a blank icon when it's clicked, so store its normal bitmap
455 	wxBitmap eyedropper_bitmap;
456 
457 	/// The point where the eyedropper was click, used to make it possible to either
458 	/// click the eyedropper or drag the eyedropper
459 	wxPoint eyedropper_grab_point;
460 
461 	bool eyedropper_is_grabbed;
462 
463 	wxStaticBitmap *preview_box; ///< A box which simply shows the current color
464 	ColorPickerRecent *recent_box; ///< A grid of recently used colors
465 
466 	ColorPickerScreenDropper *screen_dropper;
467 
468 	wxStaticBitmap *screen_dropper_icon;
469 
470 	/// Update all other controls as a result of modifying an RGB control
471 	void UpdateFromRGB(bool dirty = true);
472 	/// Update all other controls as a result of modifying an HSL control
473 	void UpdateFromHSL(bool dirty = true);
474 	/// Update all other controls as a result of modifying an HSV control
475 	void UpdateFromHSV(bool dirty = true);
476 	/// Update all other controls as a result of modifying the ASS format control
477 	void UpdateFromAss();
478 	/// Update all other controls as a result of modifying the HTML format control
479 	void UpdateFromHTML();
480 	void UpdateFromAlpha();
481 
482 	void SetRGB(agi::Color new_color);
483 	void SetHSL(unsigned char r, unsigned char g, unsigned char b);
484 	void SetHSV(unsigned char r, unsigned char g, unsigned char b);
485 
486 	/// Redraw the spectrum display
487 	void UpdateSpectrumDisplay();
488 
489 	wxBitmap *MakeGBSpectrum();
490 	wxBitmap *MakeRBSpectrum();
491 	wxBitmap *MakeRGSpectrum();
492 	wxBitmap *MakeHSSpectrum();
493 	wxBitmap *MakeSVSpectrum();
494 
495 	/// Constructor helper function for making the color input box sizers
496 	template<int N, class Control>
497 	wxSizer *MakeColorInputSizer(wxString (&labels)[N], Control *(&inputs)[N]);
498 
499 	void OnChangeMode(wxCommandEvent &evt);
500 	void OnSpectrumChange(wxCommandEvent &evt);
501 	void OnSliderChange(wxCommandEvent &evt);
502 	void OnAlphaSliderChange(wxCommandEvent &evt);
503 	void OnRecentSelect(wxThreadEvent &evt); // also handles dropper pick
504 	void OnDropperMouse(wxMouseEvent &evt);
505 	void OnMouse(wxMouseEvent &evt);
506 	void OnCaptureLost(wxMouseCaptureLostEvent&);
507 
508 	std::function<void (agi::Color)> callback;
509 
510 public:
511 	DialogColorPicker(wxWindow *parent, agi::Color initial_color, std::function<void (agi::Color)> callback, bool alpha);
512 	~DialogColorPicker();
513 
514 	void SetColor(agi::Color new_color);
515 	void AddColorToRecent();
516 };
517 
518 static const int slider_width = 10; ///< width in pixels of the color slider control
519 static const int alpha_box_size = 5;
520 
521 template<typename Func>
make_slider_img(Func func)522 static wxBitmap make_slider_img(Func func) {
523 	unsigned char *slid = (unsigned char *)calloc(slider_width * 256 * 3, 1);
524 	func(slid);
525 	wxImage img(slider_width, 256, slid);
526 	return wxBitmap(img);
527 }
528 
529 template<typename Func>
make_slider(Func func)530 static wxBitmap make_slider(Func func) {
531 	return make_slider_img([&](unsigned char *slid) {
532 		for (int y = 0; y < 256; ++y) {
533 			unsigned char rgb[3];
534 			func(y, rgb);
535 			for (int x = 0; x < slider_width; ++x)
536 				memcpy(slid + y * slider_width * 3 + x * 3, rgb, 3);
537 		}
538 	});
539 }
540 
DialogColorPicker(wxWindow * parent,agi::Color initial_color,std::function<void (agi::Color)> callback,bool alpha)541 DialogColorPicker::DialogColorPicker(wxWindow *parent, agi::Color initial_color, std::function<void (agi::Color)> callback, bool alpha)
542 : wxDialog(parent, -1, _("Select Color"))
543 , callback(std::move(callback))
544 {
545 	// generate spectrum slider bar images
546 	for (int i = 0; i < 3; ++i) {
547 		rgb_slider[i] = make_slider([=](int y, unsigned char *rgb) {
548 			memset(rgb, 0, 3);
549 			rgb[i] = y;
550 		});
551 	}
552 	hsl_slider = make_slider([](int y, unsigned char *rgb) { memset(rgb, y, 3); });
553 	hsv_slider = make_slider([](int y, unsigned char *rgb) { hsv_to_rgb(y, 255, 255, rgb, rgb + 1, rgb + 2); });
554 
555 	// Create the controls for the dialog
556 	wxSizer *spectrum_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Color spectrum"));
557 	spectrum = new ColorPickerSpectrum(this, PickerDirection::HorzVert, wxSize(256, 256));
558 	slider = new ColorPickerSpectrum(this, PickerDirection::Vert, wxSize(slider_width, 256));
559 	alpha_slider = new ColorPickerSpectrum(this, PickerDirection::Vert, wxSize(slider_width, 256));
560 	wxString modes[] = { _("RGB/R"), _("RGB/G"), _("RGB/B"), _("HSL/L"), _("HSV/H") };
561 	colorspace_choice = new wxChoice(this, -1, wxDefaultPosition, wxDefaultSize, 5, modes);
562 
563 	wxSize colorinput_size = GetTextExtent(" &H10117B& ");
564 	colorinput_size.SetHeight(-1);
565 	wxSize colorinput_labelsize(40, -1);
566 
567 	wxSizer *rgb_box = new wxStaticBoxSizer(wxHORIZONTAL, this, _("RGB color"));
568 	wxSizer *hsl_box = new wxStaticBoxSizer(wxVERTICAL, this, _("HSL color"));
569 	wxSizer *hsv_box = new wxStaticBoxSizer(wxVERTICAL, this, _("HSV color"));
570 
571 	for (auto& elem : rgb_input)
572 		elem = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255);
573 
574 	ass_input = new wxTextCtrl(this, -1, "", wxDefaultPosition, colorinput_size);
575 	html_input = new wxTextCtrl(this, -1, "", wxDefaultPosition, colorinput_size);
576 	alpha_input = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255);
577 
578 	for (auto& elem : hsl_input)
579 		elem = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255);
580 
581 	for (auto& elem : hsv_input)
582 		elem = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255);
583 
584 	preview_box = new wxStaticBitmap(this, -1, wxBitmap(40, 40, 24), wxDefaultPosition, wxSize(40, 40), STATIC_BORDER_FLAG);
585 	recent_box = new ColorPickerRecent(this, 8, 4, 16);
586 
587 	eyedropper_bitmap = GETIMAGE(eyedropper_tool_24);
588 	eyedropper_bitmap.SetMask(new wxMask(eyedropper_bitmap, wxColour(255, 0, 255)));
589 	screen_dropper_icon = new wxStaticBitmap(this, -1, eyedropper_bitmap, wxDefaultPosition, wxDefaultSize, wxRAISED_BORDER);
590 	screen_dropper = new ColorPickerScreenDropper(this, 7, 7, 8);
591 
592 	// Arrange the controls in a nice way
593 	wxSizer *spectop_sizer = new wxBoxSizer(wxHORIZONTAL);
594 	spectop_sizer->Add(new wxStaticText(this, -1, _("Spectrum mode:")), 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxRIGHT, 5);
595 	spectop_sizer->Add(colorspace_choice, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT);
596 	spectop_sizer->Add(5, 5, 1, wxEXPAND);
597 	spectop_sizer->Add(preview_box, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT);
598 
599 	wxSizer *spectrum_sizer = new wxFlexGridSizer(3, 5, 5);
600 	spectrum_sizer->Add(spectop_sizer, wxEXPAND);
601 	spectrum_sizer->AddStretchSpacer(1);
602 	spectrum_sizer->AddStretchSpacer(1);
603 	spectrum_sizer->Add(spectrum);
604 	spectrum_sizer->Add(slider);
605 	spectrum_sizer->Add(alpha_slider);
606 	if (!alpha)
607 		spectrum_sizer->Hide(alpha_slider);
608 
609 	spectrum_box->Add(spectrum_sizer, 0, wxALL, 3);
610 
611 	wxString rgb_labels[] = { _("Red:"), _("Green:"), _("Blue:") };
612 	rgb_box->Add(MakeColorInputSizer(rgb_labels, rgb_input), 1, wxALL|wxEXPAND, 3);
613 
614 	wxString ass_labels[] = { "ASS:", "HTML:", _("Alpha:") };
615 	wxControl *ass_ctrls[] = { ass_input, html_input, alpha_input };
616 	auto ass_colors_sizer = MakeColorInputSizer(ass_labels, ass_ctrls);
617 	if (!alpha)
618 		ass_colors_sizer->Hide(alpha_input);
619 	rgb_box->Add(ass_colors_sizer, 0, wxALL|wxCENTER|wxEXPAND, 3);
620 
621 	wxString hsl_labels[] = { _("Hue:"), _("Sat.:"), _("Lum.:") };
622 	hsl_box->Add(MakeColorInputSizer(hsl_labels, hsl_input), 0, wxALL|wxEXPAND, 3);
623 
624 	wxString hsv_labels[] = { _("Hue:"), _("Sat.:"), _("Value:") };
625 	hsv_box->Add(MakeColorInputSizer(hsv_labels, hsv_input), 0, wxALL|wxEXPAND, 3);
626 
627 	wxSizer *hsx_sizer = new wxBoxSizer(wxHORIZONTAL);
628 	hsx_sizer->Add(hsl_box);
629 	hsx_sizer->AddSpacer(5);
630 	hsx_sizer->Add(hsv_box);
631 
632 	wxSizer *picker_sizer = new wxBoxSizer(wxHORIZONTAL);
633 	picker_sizer->AddStretchSpacer();
634 	picker_sizer->Add(screen_dropper_icon, 0, wxALIGN_CENTER|wxRIGHT, 5);
635 	picker_sizer->Add(screen_dropper, 0, wxALIGN_CENTER);
636 	picker_sizer->AddStretchSpacer();
637 	picker_sizer->Add(recent_box, 0, wxALIGN_CENTER);
638 	picker_sizer->AddStretchSpacer();
639 
640 	wxStdDialogButtonSizer *button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP);
641 
642 	wxSizer *input_sizer = new wxBoxSizer(wxVERTICAL);
643 	input_sizer->Add(rgb_box, 0, wxALIGN_CENTER|wxEXPAND);
644 	input_sizer->AddSpacer(5);
645 	input_sizer->Add(hsx_sizer, 0, wxALIGN_CENTER|wxEXPAND);
646 	input_sizer->AddStretchSpacer(1);
647 	input_sizer->Add(picker_sizer, 0, wxALIGN_CENTER|wxEXPAND);
648 	input_sizer->AddStretchSpacer(2);
649 	input_sizer->Add(button_sizer, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM);
650 
651 	wxSizer *main_sizer = new wxBoxSizer(wxHORIZONTAL);
652 	main_sizer->Add(spectrum_box, 1, wxALL | wxEXPAND, 5);
653 	main_sizer->Add(input_sizer, 0, (wxALL&~wxLEFT)|wxEXPAND, 5);
654 
655 	SetSizerAndFit(main_sizer);
656 
657 	persist = agi::make_unique<PersistLocation>(this, "Tool/Colour Picker");
658 
659 	// Fill the controls
660 	int mode = OPT_GET("Tool/Colour Picker/Mode")->GetInt();
661 	if (mode < 0 || mode > 4) mode = 3; // HSL default
662 	colorspace_choice->SetSelection(mode);
663 	SetColor(initial_color);
664 	recent_box->Load(OPT_GET("Tool/Colour Picker/Recent Colours")->GetListColor());
665 
666 	using std::bind;
667 	for (int i = 0; i < 3; ++i) {
668 		rgb_input[i]->Bind(wxEVT_SPINCTRL, bind(&DialogColorPicker::UpdateFromRGB, this, true));
669 		rgb_input[i]->Bind(wxEVT_TEXT, bind(&DialogColorPicker::UpdateFromRGB, this, true));
670 		hsl_input[i]->Bind(wxEVT_SPINCTRL, bind(&DialogColorPicker::UpdateFromHSL, this, true));
671 		hsl_input[i]->Bind(wxEVT_TEXT, bind(&DialogColorPicker::UpdateFromHSL, this, true));
672 		hsv_input[i]->Bind(wxEVT_SPINCTRL, bind(&DialogColorPicker::UpdateFromHSV, this, true));
673 		hsv_input[i]->Bind(wxEVT_TEXT, bind(&DialogColorPicker::UpdateFromHSV, this, true));
674 	}
675 	ass_input->Bind(wxEVT_TEXT, bind(&DialogColorPicker::UpdateFromAss, this));
676 	html_input->Bind(wxEVT_TEXT, bind(&DialogColorPicker::UpdateFromHTML, this));
677 	alpha_input->Bind(wxEVT_SPINCTRL, bind(&DialogColorPicker::UpdateFromAlpha, this));
678 	alpha_input->Bind(wxEVT_TEXT, bind(&DialogColorPicker::UpdateFromAlpha, this));
679 
680 	screen_dropper_icon->Bind(wxEVT_MOTION, &DialogColorPicker::OnDropperMouse, this);
681 	screen_dropper_icon->Bind(wxEVT_LEFT_DOWN, &DialogColorPicker::OnDropperMouse, this);
682 	screen_dropper_icon->Bind(wxEVT_LEFT_UP, &DialogColorPicker::OnDropperMouse, this);
683 	screen_dropper_icon->Bind(wxEVT_MOUSE_CAPTURE_LOST, &DialogColorPicker::OnCaptureLost, this);
684 	Bind(wxEVT_MOTION, &DialogColorPicker::OnMouse, this);
685 	Bind(wxEVT_LEFT_DOWN, &DialogColorPicker::OnMouse, this);
686 	Bind(wxEVT_LEFT_UP, &DialogColorPicker::OnMouse, this);
687 
688 	spectrum->Bind(EVT_SPECTRUM_CHANGE, &DialogColorPicker::OnSpectrumChange, this);
689 	slider->Bind(EVT_SPECTRUM_CHANGE, &DialogColorPicker::OnSliderChange, this);
690 	alpha_slider->Bind(EVT_SPECTRUM_CHANGE, &DialogColorPicker::OnAlphaSliderChange, this);
691 	recent_box->Bind(EVT_RECENT_SELECT, &DialogColorPicker::OnRecentSelect, this);
692 	screen_dropper->Bind(EVT_DROPPER_SELECT, &DialogColorPicker::OnRecentSelect, this);
693 
694 	colorspace_choice->Bind(wxEVT_CHOICE, &DialogColorPicker::OnChangeMode, this);
695 
696 	button_sizer->GetHelpButton()->Bind(wxEVT_BUTTON, bind(&HelpButton::OpenPage, "Colour Picker"));
697 }
698 
699 template<int N, class Control>
MakeColorInputSizer(wxString (& labels)[N],Control * (& inputs)[N])700 wxSizer *DialogColorPicker::MakeColorInputSizer(wxString (&labels)[N], Control *(&inputs)[N]) {
701 	auto sizer = new wxFlexGridSizer(2, 5, 5);
702 	for (int i = 0; i < N; ++i) {
703 		sizer->Add(new wxStaticText(this, -1, labels[i]), wxSizerFlags(1).Center().Left());
704 		sizer->Add(inputs[i]);
705 	}
706 	sizer->AddGrowableCol(0,1);
707 	return sizer;
708 }
709 
~DialogColorPicker()710 DialogColorPicker::~DialogColorPicker() {
711 	if (screen_dropper_icon->HasCapture()) screen_dropper_icon->ReleaseMouse();
712 }
713 
change_value(wxSpinCtrl * ctrl,int value)714 static void change_value(wxSpinCtrl *ctrl, int value) {
715 	wxEventBlocker blocker(ctrl);
716 	ctrl->SetValue(value);
717 }
718 
SetColor(agi::Color new_color)719 void DialogColorPicker::SetColor(agi::Color new_color) {
720 	change_value(alpha_input, new_color.a);
721 	alpha_slider->SetXY(0, new_color.a);
722 	cur_color.a = new_color.a;
723 
724 	SetRGB(new_color);
725 	spectrum_dirty = true;
726 	UpdateFromRGB();
727 }
728 
AddColorToRecent()729 void DialogColorPicker::AddColorToRecent() {
730 	recent_box->AddColor(cur_color);
731 	OPT_SET("Tool/Colour Picker/Recent Colours")->SetListColor(recent_box->Save());
732 }
733 
SetRGB(agi::Color new_color)734 void DialogColorPicker::SetRGB(agi::Color new_color) {
735 	change_value(rgb_input[0], new_color.r);
736 	change_value(rgb_input[1], new_color.g);
737 	change_value(rgb_input[2], new_color.b);
738 	new_color.a = cur_color.a;
739 	cur_color = new_color;
740 }
741 
SetHSL(unsigned char r,unsigned char g,unsigned char b)742 void DialogColorPicker::SetHSL(unsigned char r, unsigned char g, unsigned char b) {
743 	unsigned char h, s, l;
744 	rgb_to_hsl(r, g, b, &h, &s, &l);
745 	change_value(hsl_input[0], h);
746 	change_value(hsl_input[1], s);
747 	change_value(hsl_input[2], l);
748 }
749 
SetHSV(unsigned char r,unsigned char g,unsigned char b)750 void DialogColorPicker::SetHSV(unsigned char r, unsigned char g, unsigned char b) {
751 	unsigned char h, s, v;
752 	rgb_to_hsv(r, g, b, &h, &s, &v);
753 	change_value(hsv_input[0], h);
754 	change_value(hsv_input[1], s);
755 	change_value(hsv_input[2], v);
756 }
757 
UpdateFromRGB(bool dirty)758 void DialogColorPicker::UpdateFromRGB(bool dirty) {
759 	unsigned char r = rgb_input[0]->GetValue();
760 	unsigned char g = rgb_input[1]->GetValue();
761 	unsigned char b = rgb_input[2]->GetValue();
762 	SetHSL(r, g, b);
763 	SetHSV(r, g, b);
764 	cur_color = agi::Color(r, g, b, cur_color.a);
765 	ass_input->ChangeValue(to_wx(cur_color.GetAssOverrideFormatted()));
766 	html_input->ChangeValue(to_wx(cur_color.GetHexFormatted()));
767 
768 	if (dirty)
769 		spectrum_dirty = true;
770 	UpdateSpectrumDisplay();
771 }
772 
UpdateFromHSL(bool dirty)773 void DialogColorPicker::UpdateFromHSL(bool dirty) {
774 	unsigned char r, g, b, h, s, l;
775 	h = hsl_input[0]->GetValue();
776 	s = hsl_input[1]->GetValue();
777 	l = hsl_input[2]->GetValue();
778 	hsl_to_rgb(h, s, l, &r, &g, &b);
779 	SetRGB(agi::Color(r, g, b));
780 	SetHSV(r, g, b);
781 
782 	ass_input->ChangeValue(to_wx(cur_color.GetAssOverrideFormatted()));
783 	html_input->ChangeValue(to_wx(cur_color.GetHexFormatted()));
784 
785 	if (dirty)
786 		spectrum_dirty = true;
787 	UpdateSpectrumDisplay();
788 }
789 
UpdateFromHSV(bool dirty)790 void DialogColorPicker::UpdateFromHSV(bool dirty) {
791 	unsigned char r, g, b, h, s, v;
792 	h = hsv_input[0]->GetValue();
793 	s = hsv_input[1]->GetValue();
794 	v = hsv_input[2]->GetValue();
795 	hsv_to_rgb(h, s, v, &r, &g, &b);
796 	SetRGB(agi::Color(r, g, b));
797 	SetHSL(r, g, b);
798 	ass_input->ChangeValue(to_wx(cur_color.GetAssOverrideFormatted()));
799 	html_input->ChangeValue(to_wx(cur_color.GetHexFormatted()));
800 
801 	if (dirty)
802 		spectrum_dirty = true;
803 	UpdateSpectrumDisplay();
804 }
805 
UpdateFromAss()806 void DialogColorPicker::UpdateFromAss() {
807 	agi::Color color(from_wx(ass_input->GetValue()));
808 	SetRGB(color);
809 	SetHSL(color.r, color.g, color.b);
810 	SetHSV(color.r, color.g, color.b);
811 	html_input->ChangeValue(to_wx(cur_color.GetHexFormatted()));
812 
813 	spectrum_dirty = true;
814 	UpdateSpectrumDisplay();
815 }
816 
UpdateFromHTML()817 void DialogColorPicker::UpdateFromHTML() {
818 	agi::Color color(from_wx(html_input->GetValue()));
819 	SetRGB(color);
820 	SetHSL(color.r, color.g, color.b);
821 	SetHSV(color.r, color.g, color.b);
822 	ass_input->ChangeValue(to_wx(cur_color.GetAssOverrideFormatted()));
823 
824 	spectrum_dirty = true;
825 	UpdateSpectrumDisplay();
826 }
827 
UpdateFromAlpha()828 void DialogColorPicker::UpdateFromAlpha() {
829 	cur_color.a = alpha_input->GetValue();
830 	alpha_slider->SetXY(0, cur_color.a);
831 	callback(cur_color);
832 }
833 
UpdateSpectrumDisplay()834 void DialogColorPicker::UpdateSpectrumDisplay() {
835 	int i = colorspace_choice->GetSelection();
836 	if (spectrum_dirty) {
837 		switch (i) {
838 			case 0: spectrum->SetBackground(MakeGBSpectrum(), true); break;
839 			case 1: spectrum->SetBackground(MakeRBSpectrum(), true); break;
840 			case 2: spectrum->SetBackground(MakeRGSpectrum(), true); break;
841 			case 3: spectrum->SetBackground(MakeHSSpectrum(), true); break;
842 			case 4: spectrum->SetBackground(MakeSVSpectrum(), true); break;
843 		}
844 	}
845 
846 	switch (i) {
847 		case 0: case 1: case 2:
848 			slider->SetBackground(&rgb_slider[i]);
849 			slider->SetXY(0, rgb_input[i]->GetValue());
850 			spectrum->SetXY(rgb_input[2 - (i == 2)]->GetValue(), rgb_input[1 == 0]->GetValue());
851 			break;
852 		case 3:
853 			slider->SetBackground(&hsl_slider);
854 			slider->SetXY(0, hsl_input[2]->GetValue());
855 			spectrum->SetXY(hsl_input[1]->GetValue(), hsl_input[0]->GetValue());
856 			break;
857 		case 4:
858 			slider->SetBackground(&hsv_slider);
859 			slider->SetXY(0, hsv_input[0]->GetValue());
860 			spectrum->SetXY(hsv_input[1]->GetValue(), hsv_input[2]->GetValue());
861 			break;
862 	}
863 	spectrum_dirty = false;
864 
865 	wxBitmap tempBmp = preview_box->GetBitmap();
866 	{
867 		wxMemoryDC previewdc;
868 		previewdc.SelectObject(tempBmp);
869 		previewdc.SetPen(*wxTRANSPARENT_PEN);
870 		previewdc.SetBrush(wxBrush(to_wx(cur_color)));
871 		previewdc.DrawRectangle(0, 0, 40, 40);
872 	}
873 	preview_box->SetBitmap(tempBmp);
874 
875 	alpha_slider_img = make_slider_img([=](unsigned char *slid) {
876 		static_assert(slider_width % alpha_box_size == 0, "Slider width must be a multiple of alpha box width");
877 
878 		for (int y = 0; y < 256; ++y) {
879 			unsigned char inv_y = 0xFF - y;
880 
881 			unsigned char box_colors[] = {
882 				static_cast<unsigned char>(0x66 - inv_y * 0x66 / 0xFF),
883 				static_cast<unsigned char>(0x99 - inv_y * 0x99 / 0xFF)
884 			};
885 
886 			if ((y / alpha_box_size) & 1)
887 				std::swap(box_colors[0], box_colors[1]);
888 
889 			unsigned char colors[2][3];
890 			for (int i = 0; i < 2; ++i) {
891 				colors[i][0] = cur_color.r * inv_y / 0xFF + box_colors[i];
892 				colors[i][1] = cur_color.g * inv_y / 0xFF + box_colors[i];
893 				colors[i][2] = cur_color.b * inv_y / 0xFF + box_colors[i];
894 			}
895 
896 			for (int x = 0; x < slider_width; ++x) {
897 				*slid++ = colors[x / alpha_box_size][0];
898 				*slid++ = colors[x / alpha_box_size][1];
899 				*slid++ = colors[x / alpha_box_size][2];
900 			}
901 		}
902 	});
903 	alpha_slider->SetBackground(&alpha_slider_img, true);
904 
905 	callback(cur_color);
906 }
907 
908 template<typename Func>
make_spectrum(wxBitmap * bitmap,Func func)909 static wxBitmap *make_spectrum(wxBitmap *bitmap, Func func) {
910 	wxImage spectrum_image(256, 256);
911 	func(spectrum_image.GetData());
912 	*bitmap = wxBitmap(spectrum_image);
913 	return bitmap;
914 }
915 
MakeGBSpectrum()916 wxBitmap *DialogColorPicker::MakeGBSpectrum() {
917 	return make_spectrum(&rgb_spectrum[0], [=](unsigned char *spec) {
918 		for (int g = 0; g < 256; g++) {
919 			for (int b = 0; b < 256; b++) {
920 				*spec++ = cur_color.r;
921 				*spec++ = g;
922 				*spec++ = b;
923 			}
924 		}
925 	});
926 }
927 
MakeRBSpectrum()928 wxBitmap *DialogColorPicker::MakeRBSpectrum() {
929 	return make_spectrum(&rgb_spectrum[1], [=](unsigned char *spec) {
930 		for (int r = 0; r < 256; r++) {
931 			for (int b = 0; b < 256; b++) {
932 				*spec++ = r;
933 				*spec++ = cur_color.g;
934 				*spec++ = b;
935 			}
936 		}
937 	});
938 }
939 
MakeRGSpectrum()940 wxBitmap *DialogColorPicker::MakeRGSpectrum() {
941 	return make_spectrum(&rgb_spectrum[2], [=](unsigned char *spec) {
942 		for (int r = 0; r < 256; r++) {
943 			for (int g = 0; g < 256; g++) {
944 				*spec++ = r;
945 				*spec++ = g;
946 				*spec++ = cur_color.b;
947 			}
948 		}
949 	});
950 }
951 
MakeHSSpectrum()952 wxBitmap *DialogColorPicker::MakeHSSpectrum() {
953 	int l = hsl_input[2]->GetValue();
954 	return make_spectrum(&hsl_spectrum, [=](unsigned char *spec) {
955 		for (int h = 0; h < 256; h++) {
956 			unsigned char maxr, maxg, maxb;
957 			hsl_to_rgb(h, 255, l, &maxr, &maxg, &maxb);
958 
959 			for (int s = 0; s < 256; s++) {
960 				*spec++ = maxr * s / 256 + (255-s) * l / 256;
961 				*spec++ = maxg * s / 256 + (255-s) * l / 256;
962 				*spec++ = maxb * s / 256 + (255-s) * l / 256;
963 			}
964 		}
965 	});
966 }
967 
MakeSVSpectrum()968 wxBitmap *DialogColorPicker::MakeSVSpectrum() {
969 	int h = hsv_input[0]->GetValue();
970 	unsigned char maxr, maxg, maxb;
971 	hsv_to_rgb(h, 255, 255, &maxr, &maxg, &maxb);
972 
973 	return make_spectrum(&hsv_spectrum, [=](unsigned char *spec) {
974 		for (int v = 0; v < 256; v++) {
975 			int rr = (255-maxr) * v / 256;
976 			int rg = (255-maxg) * v / 256;
977 			int rb = (255-maxb) * v / 256;
978 			for (int s = 0; s < 256; s++) {
979 				*spec++ = 255 - rr * s / 256 - (255-v);
980 				*spec++ = 255 - rg * s / 256 - (255-v);
981 				*spec++ = 255 - rb * s / 256 - (255-v);
982 			}
983 		}
984 	});
985 }
986 
OnChangeMode(wxCommandEvent &)987 void DialogColorPicker::OnChangeMode(wxCommandEvent &) {
988 	spectrum_dirty = true;
989 	OPT_SET("Tool/Colour Picker/Mode")->SetInt(colorspace_choice->GetSelection());
990 	UpdateSpectrumDisplay();
991 }
992 
OnSpectrumChange(wxCommandEvent &)993 void DialogColorPicker::OnSpectrumChange(wxCommandEvent &) {
994 	int i = colorspace_choice->GetSelection();
995 	switch (i) {
996 		case 0: case 1: case 2:
997 			change_value(rgb_input[2 - (i == 2)], spectrum->GetX());
998 			change_value(rgb_input[i == 0], spectrum->GetY());
999 			break;
1000 		case 3:
1001 			change_value(hsl_input[1], spectrum->GetX());
1002 			change_value(hsl_input[0], spectrum->GetY());
1003 			break;
1004 		case 4:
1005 			change_value(hsv_input[1], spectrum->GetX());
1006 			change_value(hsv_input[2], spectrum->GetY());
1007 			break;
1008 	}
1009 
1010 	switch (i) {
1011 		case 0: case 1: case 2:
1012 			UpdateFromRGB(false);
1013 			break;
1014 		case 3:
1015 			UpdateFromHSL(false);
1016 			break;
1017 		case 4:
1018 			UpdateFromHSV(false);
1019 			break;
1020 	}
1021 }
1022 
OnSliderChange(wxCommandEvent &)1023 void DialogColorPicker::OnSliderChange(wxCommandEvent &) {
1024 	spectrum_dirty = true;
1025 	int i = colorspace_choice->GetSelection();
1026 	switch (i) {
1027 		case 0: case 1: case 2:
1028 			change_value(rgb_input[i], slider->GetY());
1029 			UpdateFromRGB(false);
1030 			break;
1031 		case 3:
1032 			change_value(hsl_input[2], slider->GetY());
1033 			UpdateFromHSL(false);
1034 			break;
1035 		case 4:
1036 			change_value(hsv_input[0], slider->GetY());
1037 			UpdateFromHSV(false);
1038 			break;
1039 	}
1040 }
1041 
OnAlphaSliderChange(wxCommandEvent &)1042 void DialogColorPicker::OnAlphaSliderChange(wxCommandEvent &) {
1043 	change_value(alpha_input, alpha_slider->GetY());
1044 	cur_color.a = alpha_slider->GetY();
1045 	callback(cur_color);
1046 }
1047 
OnRecentSelect(wxThreadEvent & evt)1048 void DialogColorPicker::OnRecentSelect(wxThreadEvent &evt) {
1049 	agi::Color new_color = evt.GetPayload<agi::Color>();
1050 	new_color.a = cur_color.a;
1051 	SetColor(new_color);
1052 }
1053 
OnDropperMouse(wxMouseEvent & evt)1054 void DialogColorPicker::OnDropperMouse(wxMouseEvent &evt) {
1055 	if (evt.LeftDown() && !screen_dropper_icon->HasCapture()) {
1056 #ifdef WIN32
1057 		screen_dropper_icon->SetCursor(wxCursor("eyedropper_cursor"));
1058 #else
1059 		screen_dropper_icon->SetCursor(*wxCROSS_CURSOR);
1060 #endif
1061 		screen_dropper_icon->SetBitmap(wxNullBitmap);
1062 		screen_dropper_icon->CaptureMouse();
1063 		eyedropper_grab_point = evt.GetPosition();
1064 		eyedropper_is_grabbed = false;
1065 	}
1066 
1067 	if (evt.LeftUp()) {
1068 		wxPoint ptdiff = evt.GetPosition() - eyedropper_grab_point;
1069 		bool release_now = eyedropper_is_grabbed || abs(ptdiff.x) + abs(ptdiff.y) > 7;
1070 		if (release_now) {
1071 			screen_dropper_icon->ReleaseMouse();
1072 			eyedropper_is_grabbed = false;
1073 			screen_dropper_icon->SetCursor(wxNullCursor);
1074 			screen_dropper_icon->SetBitmap(eyedropper_bitmap);
1075 		}
1076 		else
1077 			eyedropper_is_grabbed = true;
1078 	}
1079 
1080 	if (screen_dropper_icon->HasCapture()) {
1081 		wxPoint scrpos = screen_dropper_icon->ClientToScreen(evt.GetPosition());
1082 		screen_dropper->DropFromScreenXY(scrpos.x, scrpos.y);
1083 	}
1084 }
1085 
1086 /// @brief Hack to redirect events to the screen dropper icon
OnMouse(wxMouseEvent & evt)1087 void DialogColorPicker::OnMouse(wxMouseEvent &evt) {
1088 	if (!screen_dropper_icon->HasCapture()) {
1089 		evt.Skip();
1090 		return;
1091 	}
1092 
1093 	wxPoint dropper_pos = screen_dropper_icon->ScreenToClient(ClientToScreen(evt.GetPosition()));
1094 	evt.m_x = dropper_pos.x;
1095 	evt.m_y = dropper_pos.y;
1096 	screen_dropper_icon->GetEventHandler()->ProcessEvent(evt);
1097 }
1098 
OnCaptureLost(wxMouseCaptureLostEvent &)1099 void DialogColorPicker::OnCaptureLost(wxMouseCaptureLostEvent&) {
1100 	eyedropper_is_grabbed = false;
1101 	screen_dropper_icon->SetCursor(wxNullCursor);
1102 	screen_dropper_icon->SetBitmap(eyedropper_bitmap);
1103 }
1104 
1105 }
1106 
GetColorFromUser(wxWindow * parent,agi::Color original,bool alpha,std::function<void (agi::Color)> callback)1107 bool GetColorFromUser(wxWindow* parent, agi::Color original, bool alpha, std::function<void (agi::Color)> callback) {
1108 	DialogColorPicker dialog(parent, original, callback, alpha);
1109 	bool ok = dialog.ShowModal() == wxID_OK;
1110 	if (!ok)
1111 		callback(original);
1112 	else
1113 		dialog.AddColorToRecent();
1114 	return ok;
1115 }
1116