// Copyright (c) 2005, Niels Martin Hansen // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Aegisub Group nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Aegisub Project http://www.aegisub.org/ // // $Id$ /// @file dialog_colorpicker.cpp /// @brief Custom colour-selection dialogue box /// @ingroup tools_ui /// #include "config.h" #ifndef AGI_PRE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #include #include "ass_style.h" #include "colorspace.h" #include "compat.h" #include "dialog_colorpicker.h" #include "help_button.h" #include "libresrc/libresrc.h" #include "main.h" #include "persist_location.h" #include "utils.h" #ifdef __WXMAC__ #include #endif /// DOCME /// @class ColorPickerSpectrum /// @brief DOCME /// /// DOCME class ColorPickerSpectrum : public wxControl { public: enum PickerDirection { HorzVert, Horz, Vert }; private: int x; int y; /// DOCME wxBitmap *background; /// DOCME PickerDirection direction; void OnPaint(wxPaintEvent &evt); void OnMouse(wxMouseEvent &evt); bool AcceptsFocusFromKeyboard() const { return false; } public: ColorPickerSpectrum(wxWindow *parent, PickerDirection direction, wxSize size); int GetX() const { return x; } int GetY() const { return y; } void SetXY(int xx, int yy); void SetBackground(wxBitmap *new_background, bool force = false); }; /// DOCME /// @class ColorPickerRecent /// @brief DOCME /// /// DOCME class ColorPickerRecent : public wxControl { int rows; ///< Number of rows of colors int cols; ///< Number of cols of colors int cellsize; ///< Width/Height of each cell /// The colors currently displayed in the control std::vector colors; /// Does the background need to be regenerated? bool background_valid; /// Bitmap storing the cached background wxBitmap background; void OnClick(wxMouseEvent &evt); void OnPaint(wxPaintEvent &evt); void OnSize(wxSizeEvent &evt); bool AcceptsFocusFromKeyboard() const { return false; } public: ColorPickerRecent(wxWindow *parent, int cols, int rows, int cellsize); /// Load the colors to show from a string void LoadFromString(const wxString &recent_string = wxString()); /// Save the colors currently shown to a string wxString StoreToString(); /// Add a color to the beginning of the recent list void AddColor(wxColour color); }; /// DOCME /// @class ColorPickerScreenDropper /// @brief DOCME /// /// DOCME class ColorPickerScreenDropper : public wxControl { /// DOCME wxBitmap capture; /// DOCME /// DOCME int resx, resy; /// DOCME int magnification; void OnMouse(wxMouseEvent &evt); void OnPaint(wxPaintEvent &evt); bool AcceptsFocusFromKeyboard() const { return false; } public: ColorPickerScreenDropper(wxWindow *parent, int resx, int resy, int magnification); void DropFromScreenXY(int x, int y); }; /// DOCME /// @class DialogColorPicker /// @brief DOCME /// /// DOCME class DialogColorPicker : public wxDialog { agi::scoped_ptr persist; wxColour cur_color; ///< Currently selected colour bool spectrum_dirty; ///< Does the spectrum image need to be regenerated? ColorPickerSpectrum *spectrum; ///< The 2D color spectrum ColorPickerSpectrum *slider; ///< The 1D slider for the color component not in the slider wxChoice *colorspace_choice; ///< The dropdown list to select colorspaces static const int slider_width = 10; ///< width in pixels of the color slider control wxSpinCtrl *rgb_input[3]; wxBitmap *rgb_spectrum[3]; ///< x/y spectrum bitmap where color "i" is excluded from wxBitmap *rgb_slider[3]; ///< z spectrum for color "i" wxSpinCtrl *hsl_input[3]; wxBitmap *hsl_spectrum; ///< h/s spectrum wxBitmap *hsl_slider; ///< l spectrum wxSpinCtrl *hsv_input[3]; wxBitmap *hsv_spectrum; ///< s/v spectrum wxBitmap *hsv_slider; ///< h spectrum wxTextCtrl *ass_input; wxTextCtrl *html_input; /// The eyedropper is set to a blank icon when it's clicked, so store its normal bitmap wxBitmap eyedropper_bitmap; /// The point where the eyedropper was click, used to make it possible to either /// click the eyedropper or drag the eyedropper wxPoint eyedropper_grab_point; /// DOCME bool eyedropper_is_grabbed; wxStaticBitmap *preview_box; ///< A box which simply shows the current color ColorPickerRecent *recent_box; ///< A grid of recently used colors /// DOCME ColorPickerScreenDropper *screen_dropper; /// DOCME wxStaticBitmap *screen_dropper_icon; /// Update all other controls as a result of modifying an RGB control void UpdateFromRGB(bool dirty = true); /// Update all other controls as a result of modifying an HSL control void UpdateFromHSL(bool dirty = true); /// Update all other controls as a result of modifying an HSV control void UpdateFromHSV(bool dirty = true); /// Update all other controls as a result of modifying the ASS format control void UpdateFromASS(); /// Update all other controls as a result of modifying the HTML format control void UpdateFromHTML(); void SetRGB(unsigned char r, unsigned char g, unsigned char b); void SetHSL(unsigned char r, unsigned char g, unsigned char b); void SetHSV(unsigned char r, unsigned char g, unsigned char b); /// Redraw the spectrum display void UpdateSpectrumDisplay(); wxBitmap *MakeGBSpectrum(); wxBitmap *MakeRBSpectrum(); wxBitmap *MakeRGSpectrum(); wxBitmap *MakeHSSpectrum(); wxBitmap *MakeSVSpectrum(); /// Constructor helper function for making the color input box sizers template wxSizer *MakeColorInputSizer(wxString (&labels)[N], Control *(&inputs)[N]); void OnChangeMode(wxCommandEvent &evt); void OnSpectrumChange(wxCommandEvent &evt); void OnSliderChange(wxCommandEvent &evt); void OnRecentSelect(wxCommandEvent &evt); // also handles dropper pick void OnDropperMouse(wxMouseEvent &evt); void OnMouse(wxMouseEvent &evt); void OnCaptureLost(wxMouseCaptureLostEvent&); ColorCallback callback; void *callbackUserdata; public: DialogColorPicker(wxWindow *parent, wxColour initial_color, ColorCallback callback = NULL, void *userdata = NULL); ~DialogColorPicker(); void SetColor(wxColour new_color); wxColour GetColor(); }; /// DOCME static const int spectrum_horz_vert_arrow_size = 4; ColorPickerSpectrum::ColorPickerSpectrum(wxWindow *parent, PickerDirection direction, wxSize size) : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE) , x(-1) , y(-1) , background(0) , direction(direction) { size.x += 2; size.y += 2; if (direction == Vert) size.x += spectrum_horz_vert_arrow_size + 1; if (direction == Horz) size.y += spectrum_horz_vert_arrow_size + 1; SetClientSize(size); SetMinSize(GetSize()); Bind(wxEVT_LEFT_DOWN, &ColorPickerSpectrum::OnMouse, this); Bind(wxEVT_LEFT_UP, &ColorPickerSpectrum::OnMouse, this); Bind(wxEVT_MOTION, &ColorPickerSpectrum::OnMouse, this); Bind(wxEVT_PAINT, &ColorPickerSpectrum::OnPaint, this); } void ColorPickerSpectrum::SetXY(int xx, int yy) { if (x != xx || y != yy) { x = xx; y = yy; Refresh(false); } } /// @brief Set the background image for this spectrum /// @param new_background New background image /// @param force Repaint even if it appears to be the same image void ColorPickerSpectrum::SetBackground(wxBitmap *new_background, bool force) { if (background == new_background && !force) return; background = new_background; Refresh(false); } wxDEFINE_EVENT(EVT_SPECTRUM_CHANGE, wxCommandEvent); void ColorPickerSpectrum::OnPaint(wxPaintEvent &) { if (!background) return; int height = background->GetHeight(); int width = background->GetWidth(); wxPaintDC dc(this); wxMemoryDC memdc; memdc.SelectObject(*background); dc.Blit(1, 1, width, height, &memdc, 0, 0); wxPoint arrow[3]; wxRect arrow_box; wxPen invpen(*wxWHITE, 3); invpen.SetCap(wxCAP_BUTT); dc.SetLogicalFunction(wxXOR); dc.SetPen(invpen); switch (direction) { case HorzVert: // Make a little cross dc.DrawLine(x-4, y+1, x+7, y+1); dc.DrawLine(x+1, y-4, x+1, y+7); break; case Horz: // Make a vertical line stretching all the way across dc.DrawLine(x+1, 1, x+1, height+1); // Points for arrow arrow[0] = wxPoint(x+1, height+2); arrow[1] = wxPoint(x+1-spectrum_horz_vert_arrow_size, height+2+spectrum_horz_vert_arrow_size); arrow[2] = wxPoint(x+1+spectrum_horz_vert_arrow_size, height+2+spectrum_horz_vert_arrow_size); arrow_box.SetLeft(0); arrow_box.SetTop(height + 2); arrow_box.SetRight(width + 1 + spectrum_horz_vert_arrow_size); arrow_box.SetBottom(height + 2 + spectrum_horz_vert_arrow_size); break; case Vert: // Make a horizontal line stretching all the way across dc.DrawLine(1, y+1, width+1, y+1); // Points for arrow arrow[0] = wxPoint(width+2, y+1); arrow[1] = wxPoint(width+2+spectrum_horz_vert_arrow_size, y+1-spectrum_horz_vert_arrow_size); arrow[2] = wxPoint(width+2+spectrum_horz_vert_arrow_size, y+1+spectrum_horz_vert_arrow_size); arrow_box.SetLeft(width + 2); arrow_box.SetTop(0); arrow_box.SetRight(width + 2 + spectrum_horz_vert_arrow_size); arrow_box.SetBottom(height + 1 + spectrum_horz_vert_arrow_size); break; } if (direction == Horz || direction == Vert) { wxBrush bgBrush; bgBrush.SetColour(GetBackgroundColour()); dc.SetLogicalFunction(wxCOPY); dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(bgBrush); dc.DrawRectangle(arrow_box); // Arrow pointing at current point dc.SetBrush(*wxBLACK_BRUSH); dc.DrawPolygon(3, arrow); } // Border around the spectrum wxPen blkpen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT), 1); blkpen.SetCap(wxCAP_BUTT); dc.SetLogicalFunction(wxCOPY); dc.SetPen(blkpen); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawRectangle(0, 0, background->GetWidth()+2, background->GetHeight()+2); } void ColorPickerSpectrum::OnMouse(wxMouseEvent &evt) { evt.Skip(); // We only care about mouse move events during a drag if (evt.Moving()) return; if (evt.LeftDown()) { CaptureMouse(); SetCursor(wxCursor(wxCURSOR_BLANK)); } else if (evt.LeftUp() && HasCapture()) { ReleaseMouse(); SetCursor(wxNullCursor); } if (evt.LeftDown() || (HasCapture() && evt.LeftIsDown())) { int newx = mid(0, evt.GetX(), GetClientSize().x - 1); int newy = mid(0, evt.GetY(), GetClientSize().y - 1); SetXY(newx, newy); wxCommandEvent evt2(EVT_SPECTRUM_CHANGE, GetId()); AddPendingEvent(evt2); } } #ifdef WIN32 #define STATIC_BORDER_FLAG wxSTATIC_BORDER #else #define STATIC_BORDER_FLAG wxSIMPLE_BORDER #endif ColorPickerRecent::ColorPickerRecent(wxWindow *parent, int cols, int rows, int cellsize) : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, STATIC_BORDER_FLAG) , rows(rows) , cols(cols) , cellsize(cellsize) , background_valid(false) { LoadFromString(); SetClientSize(cols*cellsize, rows*cellsize); SetMinSize(GetSize()); SetMaxSize(GetSize()); SetCursor(*wxCROSS_CURSOR); Bind(wxEVT_PAINT, &ColorPickerRecent::OnPaint, this); Bind(wxEVT_LEFT_DOWN, &ColorPickerRecent::OnClick, this); Bind(wxEVT_SIZE, &ColorPickerRecent::OnSize, this); } void ColorPickerRecent::LoadFromString(const wxString &recent_string) { colors.clear(); wxStringTokenizer toker(recent_string, " ", false); while (toker.HasMoreTokens()) { AssColor color; color.Parse(toker.NextToken()); color.a = 0; // opaque colors.push_back(color.GetWXColor()); } while ((int)colors.size() < rows*cols) { colors.push_back(*wxBLACK); } background_valid = false; } wxString ColorPickerRecent::StoreToString() { wxString res; for (int i = 0; i < rows*cols; i++) { res << AssColor(colors[i]).GetASSFormatted(false, false, false) << " "; } return res.Trim(true); } void ColorPickerRecent::AddColor(wxColour color) { std::vector::iterator existing = find(colors.begin(), colors.end(), color); if (existing != colors.end()) rotate(colors.begin(), existing, existing + 1); else colors.insert(colors.begin(), color); background_valid = false; Refresh(false); } wxDEFINE_EVENT(EVT_RECENT_SELECT, wxCommandEvent); void ColorPickerRecent::OnClick(wxMouseEvent &evt) { wxSize cs = GetClientSize(); int cx = evt.GetX() * cols / cs.x; int cy = evt.GetY() * rows / cs.y; if (cx < 0 || cx > cols || cy < 0 || cy > rows) return; int i = cols*cy + cx; if (i >= 0 && i < (int)colors.size()) { wxCommandEvent evnt(EVT_RECENT_SELECT, GetId()); evnt.SetString(AssColor(colors[i]).GetASSFormatted(false, false, false)); AddPendingEvent(evnt); } } void ColorPickerRecent::OnPaint(wxPaintEvent &) { wxPaintDC pdc(this); PrepareDC(pdc); if (!background_valid) { wxSize sz = pdc.GetSize(); background = wxBitmap(sz.x, sz.y); wxMemoryDC dc(background); dc.SetPen(*wxTRANSPARENT_PEN); for (int cy = 0; cy < rows; cy++) { for (int cx = 0; cx < cols; cx++) { int x = cx * cellsize; int y = cy * cellsize; dc.SetBrush(wxBrush(colors[cy * cols + cx])); dc.DrawRectangle(x, y, x+cellsize, y+cellsize); } } background_valid = true; } pdc.DrawBitmap(background, 0, 0, false); } void ColorPickerRecent::OnSize(wxSizeEvent &) { background_valid = false; Refresh(); } ColorPickerScreenDropper::ColorPickerScreenDropper(wxWindow *parent, int resx, int resy, int magnification) : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, STATIC_BORDER_FLAG) , capture(resx * magnification, resy * magnification, wxNativePixelFormat::BitsPerPixel) , resx(resx) , resy(resy) , magnification(magnification) { SetClientSize(resx*magnification, resy*magnification); SetMinSize(GetSize()); SetMaxSize(GetSize()); SetCursor(*wxCROSS_CURSOR); wxMemoryDC capdc(capture); capdc.SetPen(*wxTRANSPARENT_PEN); capdc.SetBrush(*wxWHITE_BRUSH); capdc.DrawRectangle(0, 0, capture.GetWidth(), capture.GetHeight()); Bind(wxEVT_PAINT, &ColorPickerScreenDropper::OnPaint, this); Bind(wxEVT_LEFT_DOWN, &ColorPickerScreenDropper::OnMouse, this); } wxDEFINE_EVENT(EVT_DROPPER_SELECT, wxCommandEvent); void ColorPickerScreenDropper::OnMouse(wxMouseEvent &evt) { int x = evt.GetX(); int y = evt.GetY(); if (x >= 0 && x < capture.GetWidth() && y >= 0 && y < capture.GetHeight()) { wxNativePixelData pd(capture, wxRect(x, y, 1, 1)); wxNativePixelData::Iterator pdi(pd.GetPixels()); wxColour color(pdi.Red(), pdi.Green(), pdi.Blue(), wxALPHA_OPAQUE); wxCommandEvent evnt(EVT_DROPPER_SELECT, GetId()); evnt.SetString(AssColor(color).GetASSFormatted(false, false, false)); AddPendingEvent(evnt); } } void ColorPickerScreenDropper::OnPaint(wxPaintEvent &) { wxPaintDC(this).DrawBitmap(capture, 0, 0); } void ColorPickerScreenDropper::DropFromScreenXY(int x, int y) { wxMemoryDC capdc(capture); capdc.SetPen(*wxTRANSPARENT_PEN); #ifndef __WXMAC__ wxScreenDC screen; capdc.StretchBlit(0, 0, resx * magnification, resy * magnification, &screen, x - resx / 2, y - resy / 2, resx, resy); #else // wxScreenDC doesn't work on recent versions of OS X so do it manually // Doesn't bother handling the case where the rect overlaps two monitors CGDirectDisplayID display_id; uint32_t display_count; CGGetDisplaysWithPoint(CGPointMake(x, y), 1, &display_id, &display_count); agi::scoped_holder img(CGDisplayCreateImageForRect(display_id, CGRectMake(x - resx / 2, y - resy / 2, resx, resy)), CGImageRelease); NSUInteger width = CGImageGetWidth(img); NSUInteger height = CGImageGetHeight(img); std::vector imgdata(height * width * 4); agi::scoped_holder colorspace(CGColorSpaceCreateDeviceRGB(), CGColorSpaceRelease); agi::scoped_holder bmp_context(CGBitmapContextCreate(&imgdata[0], width, height, 8, 4 * width, colorspace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big), CGContextRelease); CGContextDrawImage(bmp_context, CGRectMake(0, 0, width, height), img); for (int x = 0; x < resx; x++) { for (int y = 0; y < resy; y++) { uint8_t *pixel = &imgdata[y * width * 4 + x * 4]; capdc.SetBrush(wxBrush(wxColour(pixel[0], pixel[1], pixel[2]))); capdc.DrawRectangle(x * magnification, y * magnification, magnification, magnification); } } #endif Refresh(false); } wxColour GetColorFromUser(wxWindow *parent, wxColour original, ColorCallback callback, void* userdata) { DialogColorPicker dialog(parent, original, callback, userdata); if (dialog.ShowModal() == wxID_OK) original = dialog.GetColor(); else original = wxNullColour; if (callback) callback(userdata, original); return original; } static wxBitmap *make_rgb_image(int width, int offset) { unsigned char *oslid = (unsigned char *)calloc(width * 256 * 3, 1); unsigned char *slid = oslid + offset; for (int y = 0; y < 256; y++) { for (int x = 0; x < width; x++) { *slid = clip_colorval(y); slid += 3; } } wxImage img(width, 256, oslid); return new wxBitmap(img); } DialogColorPicker::DialogColorPicker(wxWindow *parent, wxColour initial_color, ColorCallback callback, void* userdata) : wxDialog(parent, -1, _("Select Color")) , callback(callback) , callbackUserdata(userdata) { memset(rgb_spectrum, 0, sizeof rgb_spectrum); hsl_spectrum = 0; hsv_spectrum = 0; // generate spectrum slider bar images rgb_slider[0] = make_rgb_image(slider_width, 0); rgb_slider[1] = make_rgb_image(slider_width, 1); rgb_slider[2] = make_rgb_image(slider_width, 2); // luminance unsigned char *slid = (unsigned char *)malloc(slider_width*256*3); for (int y = 0; y < 256; y++) { memset(slid + y * slider_width * 3, y, slider_width * 3); } wxImage sliderimg(slider_width, 256, slid); hsl_slider = new wxBitmap(sliderimg); slid = (unsigned char *)malloc(slider_width*256*3); for (int y = 0; y < 256; y++) { unsigned char rgb[3]; hsv_to_rgb(y, 255, 255, rgb, rgb + 1, rgb + 2); for (int x = 0; x < slider_width; x++) { memcpy(slid + y * slider_width * 3 + x * 3, rgb, 3); } } sliderimg.SetData(slid); hsv_slider = new wxBitmap(sliderimg); // Create the controls for the dialog wxSizer *spectrum_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Color spectrum")); spectrum = new ColorPickerSpectrum(this, ColorPickerSpectrum::HorzVert, wxSize(256, 256)); slider = new ColorPickerSpectrum(this, ColorPickerSpectrum::Vert, wxSize(slider_width, 256)); wxString modes[] = { _("RGB/R"), _("RGB/G"), _("RGB/B"), _("HSL/L"), _("HSV/H") }; colorspace_choice = new wxChoice(this, -1, wxDefaultPosition, wxDefaultSize, 5, modes); wxSize colorinput_size(70, -1); wxSize colorinput_labelsize(40, -1); wxSizer *rgb_box = new wxStaticBoxSizer(wxHORIZONTAL, this, _("RGB color")); wxSizer *hsl_box = new wxStaticBoxSizer(wxVERTICAL, this, _("HSL color")); wxSizer *hsv_box = new wxStaticBoxSizer(wxVERTICAL, this, _("HSV color")); for (int i = 0; i < 3; ++i) rgb_input[i] = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255); ass_input = new wxTextCtrl(this, -1, "", wxDefaultPosition, colorinput_size); html_input = new wxTextCtrl(this, -1, "", wxDefaultPosition, colorinput_size); for (int i = 0; i < 3; ++i) hsl_input[i] = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255); for (int i = 0; i < 3; ++i) hsv_input[i] = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255); preview_box = new wxStaticBitmap(this, -1, wxBitmap(40, 40, 24), wxDefaultPosition, wxSize(40, 40), STATIC_BORDER_FLAG); recent_box = new ColorPickerRecent(this, 8, 4, 16); eyedropper_bitmap = GETIMAGE(eyedropper_tool_24); eyedropper_bitmap.SetMask(new wxMask(eyedropper_bitmap, wxColour(255, 0, 255))); screen_dropper_icon = new wxStaticBitmap(this, -1, eyedropper_bitmap, wxDefaultPosition, wxDefaultSize, wxRAISED_BORDER); screen_dropper = new ColorPickerScreenDropper(this, 7, 7, 8); // Arrange the controls in a nice way wxSizer *spectop_sizer = new wxBoxSizer(wxHORIZONTAL); spectop_sizer->Add(new wxStaticText(this, -1, _("Spectrum mode:")), 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxRIGHT, 5); spectop_sizer->Add(colorspace_choice, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT); spectop_sizer->Add(5, 5, 1, wxEXPAND); spectop_sizer->Add(preview_box, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT); wxSizer *spectrum_sizer = new wxFlexGridSizer(2, 5, 5); spectrum_sizer->Add(spectop_sizer, wxEXPAND); spectrum_sizer->AddStretchSpacer(1); spectrum_sizer->Add(spectrum); spectrum_sizer->Add(slider); spectrum_box->Add(spectrum_sizer, 0, wxALL, 3); wxString rgb_labels[] = { _("Red:"), _("Green:"), _("Blue:") }; rgb_box->Add(MakeColorInputSizer(rgb_labels, rgb_input), 1, wxALL|wxEXPAND, 3); wxString ass_labels[] = { "ASS:", "HTML:" }; wxTextCtrl *ass_ctrls[] = { ass_input, html_input }; rgb_box->Add(MakeColorInputSizer(ass_labels, ass_ctrls), 0, wxALL|wxCENTER|wxEXPAND, 3); wxString hsl_labels[] = { _("Hue:"), _("Sat.:"), _("Lum.:") }; hsl_box->Add(MakeColorInputSizer(hsl_labels, hsl_input), 0, wxALL|wxEXPAND, 3); wxString hsv_labels[] = { _("Hue:"), _("Sat.:"), _("Value:") }; hsv_box->Add(MakeColorInputSizer(hsv_labels, hsv_input), 0, wxALL|wxEXPAND, 3); wxSizer *hsx_sizer = new wxBoxSizer(wxHORIZONTAL); hsx_sizer->Add(hsl_box); hsx_sizer->AddSpacer(5); hsx_sizer->Add(hsv_box); wxSizer *recent_sizer = new wxBoxSizer(wxVERTICAL); recent_sizer->Add(recent_box, 1, wxEXPAND); wxSizer *picker_sizer = new wxBoxSizer(wxHORIZONTAL); picker_sizer->AddStretchSpacer(); picker_sizer->Add(screen_dropper_icon, 0, wxALIGN_CENTER|wxRIGHT, 5); picker_sizer->Add(screen_dropper, 0, wxALIGN_CENTER); picker_sizer->AddStretchSpacer(); picker_sizer->Add(recent_sizer, 0, wxALIGN_CENTER); picker_sizer->AddStretchSpacer(); wxStdDialogButtonSizer *button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP); wxSizer *input_sizer = new wxBoxSizer(wxVERTICAL); input_sizer->Add(rgb_box, 0, wxALIGN_CENTER|wxEXPAND); input_sizer->AddSpacer(5); input_sizer->Add(hsx_sizer, 0, wxALIGN_CENTER|wxEXPAND); input_sizer->AddStretchSpacer(1); input_sizer->Add(picker_sizer, 0, wxALIGN_CENTER|wxEXPAND); input_sizer->AddStretchSpacer(2); input_sizer->Add(button_sizer, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM); wxSizer *main_sizer = new wxBoxSizer(wxHORIZONTAL); main_sizer->Add(spectrum_box, 1, wxALL | wxEXPAND, 5); main_sizer->Add(input_sizer, 0, (wxALL&~wxLEFT)|wxEXPAND, 5); SetSizerAndFit(main_sizer); persist.reset(new PersistLocation(this, "Tool/Colour Picker")); // Fill the controls int mode = OPT_GET("Tool/Colour Picker/Mode")->GetInt(); if (mode < 0 || mode > 4) mode = 3; // HSL default colorspace_choice->SetSelection(mode); SetColor(initial_color); recent_box->LoadFromString(lagi_wxString(OPT_GET("Tool/Colour Picker/Recent")->GetString())); using std::tr1::bind; for (int i = 0; i < 3; ++i) { rgb_input[i]->Bind(wxEVT_COMMAND_SPINCTRL_UPDATED, bind(&DialogColorPicker::UpdateFromRGB, this, true)); rgb_input[i]->Bind(wxEVT_COMMAND_TEXT_UPDATED, bind(&DialogColorPicker::UpdateFromRGB, this, true)); hsl_input[i]->Bind(wxEVT_COMMAND_SPINCTRL_UPDATED, bind(&DialogColorPicker::UpdateFromHSL, this, true)); hsl_input[i]->Bind(wxEVT_COMMAND_TEXT_UPDATED, bind(&DialogColorPicker::UpdateFromHSL, this, true)); hsv_input[i]->Bind(wxEVT_COMMAND_SPINCTRL_UPDATED, bind(&DialogColorPicker::UpdateFromHSV, this, true)); hsv_input[i]->Bind(wxEVT_COMMAND_TEXT_UPDATED, bind(&DialogColorPicker::UpdateFromHSV, this, true)); } ass_input->Bind(wxEVT_COMMAND_TEXT_UPDATED, bind(&DialogColorPicker::UpdateFromASS, this)); html_input->Bind(wxEVT_COMMAND_TEXT_UPDATED, bind(&DialogColorPicker::UpdateFromHTML, this)); screen_dropper_icon->Bind(wxEVT_MOTION, &DialogColorPicker::OnDropperMouse, this); screen_dropper_icon->Bind(wxEVT_LEFT_DOWN, &DialogColorPicker::OnDropperMouse, this); screen_dropper_icon->Bind(wxEVT_LEFT_UP, &DialogColorPicker::OnDropperMouse, this); screen_dropper_icon->Bind(wxEVT_MOUSE_CAPTURE_LOST, &DialogColorPicker::OnCaptureLost, this); Bind(wxEVT_MOTION, &DialogColorPicker::OnMouse, this); Bind(wxEVT_LEFT_DOWN, &DialogColorPicker::OnMouse, this); Bind(wxEVT_LEFT_UP, &DialogColorPicker::OnMouse, this); spectrum->Bind(EVT_SPECTRUM_CHANGE, &DialogColorPicker::OnSpectrumChange, this); slider->Bind(EVT_SPECTRUM_CHANGE, &DialogColorPicker::OnSliderChange, this); recent_box->Bind(EVT_RECENT_SELECT, &DialogColorPicker::OnRecentSelect, this); screen_dropper->Bind(EVT_DROPPER_SELECT, &DialogColorPicker::OnRecentSelect, this); colorspace_choice->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &DialogColorPicker::OnChangeMode, this); button_sizer->GetHelpButton()->Bind(wxEVT_COMMAND_BUTTON_CLICKED, bind(&HelpButton::OpenPage, "Colour Picker")); } template wxSizer *DialogColorPicker::MakeColorInputSizer(wxString (&labels)[N], Control *(&inputs)[N]) { wxFlexGridSizer * sizer = new wxFlexGridSizer(2, 5, 5); for (int i = 0; i < N; ++i) { sizer->Add(new wxStaticText(this, -1, labels[i]), wxSizerFlags(1).Center().Left()); sizer->Add(inputs[i]); } sizer->AddGrowableCol(0,1); return sizer; } /// @brief Destructor DialogColorPicker::~DialogColorPicker() { delete rgb_spectrum[0]; delete rgb_spectrum[1]; delete rgb_spectrum[2]; delete hsl_spectrum; delete hsv_spectrum; delete rgb_slider[0]; delete rgb_slider[1]; delete rgb_slider[2]; delete hsl_slider; delete hsv_slider; if (screen_dropper_icon->HasCapture()) screen_dropper_icon->ReleaseMouse(); } /// @brief Sets the currently selected color, and updates all controls void DialogColorPicker::SetColor(wxColour new_color) { SetRGB(new_color.Red(), new_color.Green(), new_color.Blue()); spectrum_dirty = true; UpdateFromRGB(); } /// @brief Get the currently selected color wxColour DialogColorPicker::GetColor() { recent_box->AddColor(cur_color); OPT_SET("Tool/Colour Picker/Recent")->SetString(STD_STR(recent_box->StoreToString())); return cur_color; } static void change_value(wxSpinCtrl *ctrl, int value) { wxEventBlocker blocker(ctrl); ctrl->SetValue(value); } void DialogColorPicker::SetRGB(unsigned char r, unsigned char g, unsigned char b) { change_value(rgb_input[0], r); change_value(rgb_input[1], g); change_value(rgb_input[2], b); cur_color = wxColour(r, g, b, wxALPHA_OPAQUE); } void DialogColorPicker::SetHSL(unsigned char r, unsigned char g, unsigned char b) { unsigned char h, s, l; rgb_to_hsl(r, g, b, &h, &s, &l); change_value(hsl_input[0], h); change_value(hsl_input[1], s); change_value(hsl_input[2], l); } void DialogColorPicker::SetHSV(unsigned char r, unsigned char g, unsigned char b) { unsigned char h, s, v; rgb_to_hsv(r, g, b, &h, &s, &v); change_value(hsv_input[0], h); change_value(hsv_input[1], s); change_value(hsv_input[2], v); } /// @brief Use the values entered in the RGB controls to update the other controls void DialogColorPicker::UpdateFromRGB(bool dirty) { unsigned char r, g, b; r = rgb_input[0]->GetValue(); g = rgb_input[1]->GetValue(); b = rgb_input[2]->GetValue(); SetHSL(r, g, b); SetHSV(r, g, b); cur_color = wxColour(r, g, b, wxALPHA_OPAQUE); ass_input->ChangeValue(AssColor(cur_color).GetASSFormatted(false, false, false)); html_input->ChangeValue(color_to_html(cur_color)); if (dirty) spectrum_dirty = true; UpdateSpectrumDisplay(); } /// @brief Use the values entered in the HSL controls to update the other controls void DialogColorPicker::UpdateFromHSL(bool dirty) { unsigned char r, g, b, h, s, l; h = hsl_input[0]->GetValue(); s = hsl_input[1]->GetValue(); l = hsl_input[2]->GetValue(); hsl_to_rgb(h, s, l, &r, &g, &b); SetRGB(r, g, b); SetHSV(r, g, b); ass_input->ChangeValue(AssColor(cur_color).GetASSFormatted(false, false, false)); html_input->ChangeValue(color_to_html(cur_color)); if (dirty) spectrum_dirty = true; UpdateSpectrumDisplay(); } /// @brief Use the values entered in the HSV controls to update the other controls void DialogColorPicker::UpdateFromHSV(bool dirty) { unsigned char r, g, b, h, s, v; h = hsv_input[0]->GetValue(); s = hsv_input[1]->GetValue(); v = hsv_input[2]->GetValue(); hsv_to_rgb(h, s, v, &r, &g, &b); SetRGB(r, g, b); SetHSL(r, g, b); ass_input->ChangeValue(AssColor(cur_color).GetASSFormatted(false, false, false)); html_input->ChangeValue(color_to_html(cur_color)); if (dirty) spectrum_dirty = true; UpdateSpectrumDisplay(); } /// @brief Use the value entered in the ASS hex control to update the other controls void DialogColorPicker::UpdateFromASS() { unsigned char r, g, b; AssColor ass; ass.Parse(ass_input->GetValue()); r = ass.r; g = ass.g; b = ass.b; SetRGB(r, g, b); SetHSL(r, g, b); SetHSV(r, g, b); html_input->ChangeValue(color_to_html(cur_color)); spectrum_dirty = true; UpdateSpectrumDisplay(); } /// @brief Use the value entered in the HTML hex control to update the other controls void DialogColorPicker::UpdateFromHTML() { unsigned char r, g, b; cur_color = html_to_color(html_input->GetValue()); r = cur_color.Red(); g = cur_color.Green(); b = cur_color.Blue(); SetRGB(r, g, b); SetHSL(r, g, b); SetHSV(r, g, b); ass_input->ChangeValue(AssColor(cur_color).GetASSFormatted(false, false, false)); spectrum_dirty = true; UpdateSpectrumDisplay(); } void DialogColorPicker::UpdateSpectrumDisplay() { int i = colorspace_choice->GetSelection(); switch (i) { case 0: if (spectrum_dirty) spectrum->SetBackground(MakeGBSpectrum(), true); slider->SetBackground(rgb_slider[0]); slider->SetXY(0, rgb_input[0]->GetValue()); spectrum->SetXY(rgb_input[2]->GetValue(), rgb_input[1]->GetValue()); break; case 1: if (spectrum_dirty) spectrum->SetBackground(MakeRBSpectrum(), true); slider->SetBackground(rgb_slider[1]); slider->SetXY(0, rgb_input[1]->GetValue()); spectrum->SetXY(rgb_input[2]->GetValue(), rgb_input[0]->GetValue()); break; case 2: if (spectrum_dirty) spectrum->SetBackground(MakeRGSpectrum(), true); slider->SetBackground(rgb_slider[2]); slider->SetXY(0, rgb_input[2]->GetValue()); spectrum->SetXY(rgb_input[1]->GetValue(), rgb_input[0]->GetValue()); break; case 3: if (spectrum_dirty) spectrum->SetBackground(MakeHSSpectrum(), true); slider->SetBackground(hsl_slider); slider->SetXY(0, hsl_input[2]->GetValue()); spectrum->SetXY(hsl_input[1]->GetValue(), hsl_input[0]->GetValue()); break; case 4: if (spectrum_dirty) spectrum->SetBackground(MakeSVSpectrum(), true); slider->SetBackground(hsv_slider); slider->SetXY(0, hsv_input[0]->GetValue()); spectrum->SetXY(hsv_input[1]->GetValue(), hsv_input[2]->GetValue()); break; } spectrum_dirty = false; wxBitmap tempBmp = preview_box->GetBitmap(); { wxMemoryDC previewdc; previewdc.SelectObject(tempBmp); previewdc.SetPen(*wxTRANSPARENT_PEN); previewdc.SetBrush(wxBrush(cur_color)); previewdc.DrawRectangle(0, 0, 40, 40); } preview_box->SetBitmap(tempBmp); if (callback) callback(callbackUserdata, cur_color); } wxBitmap *DialogColorPicker::MakeGBSpectrum() { delete rgb_spectrum[0]; wxImage spectrum_image(256, 256, false); unsigned char *ospec, *spec; ospec = spec = (unsigned char *)malloc(256*256*3); if (!spec) throw std::bad_alloc(); for (int g = 0; g < 256; g++) { for (int b = 0; b < 256; b++) { *spec++ = cur_color.Red(); *spec++ = g; *spec++ = b; } } spectrum_image.SetData(ospec); return rgb_spectrum[0] = new wxBitmap(spectrum_image); } wxBitmap *DialogColorPicker::MakeRBSpectrum() { delete rgb_spectrum[1]; unsigned char *ospec, *spec; ospec = spec = (unsigned char *)malloc(256*256*3); if (!spec) throw std::bad_alloc(); for (int r = 0; r < 256; r++) { for (int b = 0; b < 256; b++) { *spec++ = r; *spec++ = cur_color.Green(); *spec++ = b; } } wxImage spectrum_image(256, 256, ospec); return rgb_spectrum[1] = new wxBitmap(spectrum_image); } wxBitmap *DialogColorPicker::MakeRGSpectrum() { delete rgb_spectrum[2]; unsigned char *ospec, *spec; ospec = spec = (unsigned char *)malloc(256*256*3); if (!spec) throw std::bad_alloc(); for (int r = 0; r < 256; r++) { for (int g = 0; g < 256; g++) { *spec++ = r; *spec++ = g; *spec++ = cur_color.Blue(); } } wxImage spectrum_image(256, 256, ospec); return rgb_spectrum[2] = new wxBitmap(spectrum_image); } wxBitmap *DialogColorPicker::MakeHSSpectrum() { delete hsl_spectrum; unsigned char *ospec, *spec; ospec = spec = (unsigned char *)malloc(256*256*3); if (!spec) throw std::bad_alloc(); int l = hsl_input[2]->GetValue(); for (int h = 0; h < 256; h++) { unsigned char maxr, maxg, maxb; hsl_to_rgb(h, 255, l, &maxr, &maxg, &maxb); for (int s = 0; s < 256; s++) { *spec++ = maxr * s / 256 + (255-s) * l / 256; *spec++ = maxg * s / 256 + (255-s) * l / 256; *spec++ = maxb * s / 256 + (255-s) * l / 256; } } wxImage spectrum_image(256, 256, ospec); return hsl_spectrum = new wxBitmap(spectrum_image); } wxBitmap *DialogColorPicker::MakeSVSpectrum() { delete hsv_spectrum; unsigned char *ospec, *spec; ospec = spec = (unsigned char *)malloc(256*256*3); if (!spec) throw std::bad_alloc(); int h = hsv_input[0]->GetValue(); unsigned char maxr, maxg, maxb; hsv_to_rgb(h, 255, 255, &maxr, &maxg, &maxb); for (int v = 0; v < 256; v++) { int rr = (255-maxr) * v / 256; int rg = (255-maxg) * v / 256; int rb = (255-maxb) * v / 256; for (int s = 0; s < 256; s++) { *spec++ = 255 - rr * s / 256 - (255-v); *spec++ = 255 - rg * s / 256 - (255-v); *spec++ = 255 - rb * s / 256 - (255-v); } } wxImage spectrum_image(256, 256, ospec); return hsv_spectrum = new wxBitmap(spectrum_image); } void DialogColorPicker::OnChangeMode(wxCommandEvent &) { spectrum_dirty = true; OPT_SET("Tool/Colour Picker/Mode")->SetInt(colorspace_choice->GetSelection()); UpdateSpectrumDisplay(); } void DialogColorPicker::OnSpectrumChange(wxCommandEvent &) { int i = colorspace_choice->GetSelection(); switch (i) { case 0: change_value(rgb_input[2], spectrum->GetX()); change_value(rgb_input[1], spectrum->GetY()); break; case 1: change_value(rgb_input[2], spectrum->GetX()); change_value(rgb_input[0], spectrum->GetY()); break; case 2: change_value(rgb_input[1], spectrum->GetX()); change_value(rgb_input[0], spectrum->GetY()); break; case 3: change_value(hsl_input[1], spectrum->GetX()); change_value(hsl_input[0], spectrum->GetY()); break; case 4: change_value(hsv_input[1], spectrum->GetX()); change_value(hsv_input[2], spectrum->GetY()); break; } switch (i) { case 0: case 1: case 2: UpdateFromRGB(false); break; case 3: UpdateFromHSL(false); break; case 4: UpdateFromHSV(false); break; } } void DialogColorPicker::OnSliderChange(wxCommandEvent &) { spectrum_dirty = true; int i = colorspace_choice->GetSelection(); switch (i) { case 0: case 1: case 2: change_value(rgb_input[i], slider->GetY()); UpdateFromRGB(false); break; case 3: change_value(hsl_input[2], slider->GetY()); UpdateFromHSL(false); break; case 4: change_value(hsv_input[0], slider->GetY()); UpdateFromHSV(false); break; } } void DialogColorPicker::OnRecentSelect(wxCommandEvent &evt) { // The colour picked is stored in the event string // Allows this event handler to be shared by recent and dropper controls // Ugly hack? AssColor color; color.Parse(evt.GetString()); SetColor(color.GetWXColor()); } void DialogColorPicker::OnDropperMouse(wxMouseEvent &evt) { if (evt.LeftDown() && !screen_dropper_icon->HasCapture()) { #ifdef WIN32 screen_dropper_icon->SetCursor(wxCursor("eyedropper_cursor")); #else screen_dropper_icon->SetCursor(*wxCROSS_CURSOR); #endif screen_dropper_icon->SetBitmap(wxNullBitmap); screen_dropper_icon->CaptureMouse(); eyedropper_grab_point = evt.GetPosition(); eyedropper_is_grabbed = false; } if (evt.LeftUp()) { wxPoint ptdiff = evt.GetPosition() - eyedropper_grab_point; bool release_now = eyedropper_is_grabbed || abs(ptdiff.x) + abs(ptdiff.y) > 7; if (release_now) { screen_dropper_icon->ReleaseMouse(); eyedropper_is_grabbed = false; screen_dropper_icon->SetCursor(wxNullCursor); screen_dropper_icon->SetBitmap(eyedropper_bitmap); } else { eyedropper_is_grabbed = true; } } if (screen_dropper_icon->HasCapture()) { wxPoint scrpos = screen_dropper_icon->ClientToScreen(evt.GetPosition()); screen_dropper->DropFromScreenXY(scrpos.x, scrpos.y); } } /// @brief Hack to redirect events to the screen dropper icon void DialogColorPicker::OnMouse(wxMouseEvent &evt) { if (screen_dropper_icon->HasCapture()) { wxPoint dropper_pos = screen_dropper_icon->ScreenToClient(ClientToScreen(evt.GetPosition())); evt.m_x = dropper_pos.x; evt.m_y = dropper_pos.y; screen_dropper_icon->GetEventHandler()->ProcessEvent(evt); } else evt.Skip(); } void DialogColorPicker::OnCaptureLost(wxMouseCaptureLostEvent&) { eyedropper_is_grabbed = false; screen_dropper_icon->SetCursor(wxNullCursor); screen_dropper_icon->SetBitmap(eyedropper_bitmap); }