From f344b8a1dbc3bcad55a3dec5682982069d45a002 Mon Sep 17 00:00:00 2001 From: twanvl Date: Sun, 26 Nov 2006 23:23:51 +0000 Subject: [PATCH] ImageValueEditor; slice window; fixed bug in resample git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@97 0fc631ac-6414-0410-93d0-97cfa31319b6 --- src/gfx/resample_image.cpp | 4 +- src/gui/control/card_list.cpp | 29 +- src/gui/control/card_list.hpp | 7 +- src/gui/image_slice_window.cpp | 618 +++++++++++++++++++++++++++++++++ src/gui/image_slice_window.hpp | 183 ++++++++++ src/gui/value/editor.hpp | 2 +- src/gui/value/image.cpp | 64 +++- src/gui/value/image.hpp | 14 + 8 files changed, 902 insertions(+), 19 deletions(-) create mode 100644 src/gui/image_slice_window.cpp create mode 100644 src/gui/image_slice_window.hpp diff --git a/src/gfx/resample_image.cpp b/src/gfx/resample_image.cpp index e6723067..38e540be 100644 --- a/src/gfx/resample_image.cpp +++ b/src/gfx/resample_image.cpp @@ -93,8 +93,8 @@ void resample_pass(const Image& img_in, Image& img_out, int offset_in, int offse if (out_rem > 0) { // eat a partial input pixel totR += in[0] * out_rem; - totR += in[1] * out_rem; - totR += in[2] * out_rem; + totG += in[1] * out_rem; + totB += in[2] * out_rem; in_rem -= out_rem; } // store diff --git a/src/gui/control/card_list.cpp b/src/gui/control/card_list.cpp index 526ecaad..d21c118e 100644 --- a/src/gui/control/card_list.cpp +++ b/src/gui/control/card_list.cpp @@ -175,30 +175,35 @@ bool CardListBase::canPaste() const { return wxTheClipboard->IsSupported(CardDataObject::format); } -void CardListBase::doCopy() { - if (!canCopy()) return; - if (!wxTheClipboard->Open()) return; - wxTheClipboard->SetData(new CardOnClipboard(set, selected_card)); // ignore result +bool CardListBase::doCopy() { + if (!canCopy()) return false; + if (!wxTheClipboard->Open()) return false; + bool ok = wxTheClipboard->SetData(new CardOnClipboard(set, selected_card)); // ignore result wxTheClipboard->Close(); + return ok; } -void CardListBase::doCut() { +bool CardListBase::doCut() { // cut = copy + delete - if (!canCut()) return; - doCopy(); - set->actions.add(new RemoveCardAction(*set, selected_card) ); + if (!canCut()) return false; + if (!doCopy()) return false; + set->actions.add(new RemoveCardAction(*set, selected_card)); + return true; } -void CardListBase::doPaste() { +bool CardListBase::doPaste() { // get data - if (!canPaste()) return; - if (!wxTheClipboard->Open()) return; + if (!canPaste()) return false; + if (!wxTheClipboard->Open()) return false; CardDataObject data; bool ok = wxTheClipboard->GetData(data); wxTheClipboard->Close(); - if (!ok) return; + if (!ok) return false; // add card to set CardP card = data.getCard(set); if (card) { set->actions.add(new AddCardAction(*set, card)); + return true; + } else { + return false; } } diff --git a/src/gui/control/card_list.hpp b/src/gui/control/card_list.hpp index 8adfe244..7664000a 100644 --- a/src/gui/control/card_list.hpp +++ b/src/gui/control/card_list.hpp @@ -69,9 +69,10 @@ class CardListBase : public wxListView, public SetView { bool canCut() const; bool canCopy() const; bool canPaste() const; - void doCut(); - void doCopy(); - void doPaste(); + // Try to perform a clipboard operation, return success + bool doCut(); + bool doCopy(); + bool doPaste(); // --------------------------------------------------- : Set actions diff --git a/src/gui/image_slice_window.cpp b/src/gui/image_slice_window.cpp new file mode 100644 index 00000000..344864c2 --- /dev/null +++ b/src/gui/image_slice_window.cpp @@ -0,0 +1,618 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- : ImageSlice + +ImageSlice::ImageSlice(const Image& source, const wxSize& target_size) + : source(source), target_size(target_size) + , selection(0, 0, source.GetWidth(), source.GetHeight()) + , allow_outside(false), aspect_fixed(true) + , sharpen(true), sharpen_amount(25) +{} + +void ImageSlice::constrain() { + sharpen_amount = min(100, max(0, sharpen_amount)); + // minimum size + selection.width = max(1, selection.width); + selection.height = max(1, selection.height); + // inside source + if (!allow_outside) { + selection.width = min(selection.width, source.GetWidth()); + selection.height = min(selection.height, source.GetHeight()); + selection.x = max(selection.x, 0); + selection.y = max(selection.y, 0); + selection.x = min(selection.x, source.GetWidth() - selection.width); + selection.y = min(selection.y, source.GetHeight() - selection.height); + } + // fix aspect ratio + if (aspect_fixed) { + int diff = selection.width * target_size.GetHeight() - selection.height * target_size.GetWidth(); + if (diff > 0) { + // too wide + selection.width -= diff / target_size.GetHeight(); + } else { + // too high + selection.height -= -diff / target_size.GetWidth(); + } + } +} + +Image ImageSlice::getSlice() const { + if (selection.width == target_size.GetWidth() && selection.height == target_size.GetHeight() && selection.x == 0 && selection.y == 0) { + // exactly the right size + return source; + } + Image target(target_size.GetWidth(), target_size.GetHeight(), false); +// if (sharpen && sharpen_amount > 0) { +// sharp_resample_and_clip(source, target, selection, sharpen_amount); +// } else { + resample_and_clip(source, target, selection); +// } + return target; +} + +// ----------------------------------------------------------------------------- : ImageSliceWindow + +ImageSliceWindow::ImageSliceWindow(Window* parent, const Image& source, const wxSize& target_size) + : wxDialog(parent,wxID_ANY,_("Slice image"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE) + , slice(source, target_size) +{ + // init controls + const wxPoint defPos = wxDefaultPosition; + const wxSize spinSize(80,-1); + selector = new ImageSliceSelector(this, ID_SELECTOR, slice); + preview = new ImageSlicePreview (this, ID_PREVIEW, slice); + + String sizes[] = { _("&Original Size") + , _("Size to &Fit") + , _("F&orce to Fit") + , _("&Custom Size") }; + size = new wxRadioBox(this, ID_SIZE, _("Size"), defPos, wxDefaultSize, 4, sizes, 1); + + left = new wxSpinCtrl(this, ID_LEFT, _(""), defPos, spinSize); + top = new wxSpinCtrl(this, ID_TOP, _(""), defPos, spinSize); + width = new wxSpinCtrl(this, ID_WIDTH, _(""), defPos, spinSize); + height = new wxSpinCtrl(this, ID_HEIGHT, _(""), defPos, spinSize); + top ->SetRange(-5000,5000); + left ->SetRange(-5000,5000); + width ->SetRange(0,5000); + height->SetRange(0,5000); + + fix_aspect = new wxCheckBox(this, ID_FIX_ASPECT, _("Fix aspect ratio (width/height)")); + zoom_x = new wxSpinCtrl(this, ID_ZOOM_X, _(""), defPos, spinSize); + zoom_y = new wxSpinCtrl(this, ID_ZOOM_Y, _(""), defPos, spinSize); + zoom = new wxSpinCtrl(this, ID_ZOOM, _(""), defPos, spinSize); + zoom_x->SetRange(1,10000); + zoom_y->SetRange(1,10000); + zoom ->SetRange(1,10000); + + sharpen = new wxCheckBox(this, ID_SHARPEN, _("&Sharpen Filter")); + sharpen_amount = new wxSlider(this, ID_SHARPEN_AMOUNT, 0, 0, 100); +// allowOutside= new CheckBox(&this, idSliceAllowOutside, _("Allow selection outside source")) +// bgColor = new ColorSelector(&this, wxID_ANY) + + // init sizers + wxSizer* s = new wxBoxSizer(wxVERTICAL); + // top row: image editors + wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL); + wxSizer* s3 = new wxBoxSizer(wxVERTICAL); + s3->Add(new wxStaticText(this, wxID_ANY, _("Original:"))); + s3->Add(selector, 1, wxEXPAND | wxTOP, 4); + s2->Add(s3, 1, wxEXPAND | wxALL, 4); + wxSizer* s4 = new wxBoxSizer(wxVERTICAL); + s4->Add(new wxStaticText(this, wxID_ANY, _("Result:"))); + s4->Add(preview, 0, wxTOP, 4); + s2->Add(s4, 0, wxALL, 4); + s->Add(s2, 1, wxEXPAND); + // bottom row: controls + wxSizer* s5 = new wxBoxSizer(wxHORIZONTAL); + s5->AddStretchSpacer(1); + s5->Add(size, 0, wxEXPAND | wxALL, 4); + s5->AddStretchSpacer(1); + wxSizer* s6 = new wxStaticBoxSizer(wxVERTICAL, this, _("Selection")); + wxSizer* s7 = new wxFlexGridSizer(0, 2, 4, 5); + s7->Add(new wxStaticText(this, wxID_ANY, _("&Left")), 0, wxALIGN_CENTER_VERTICAL); + s7->Add(left, 0, wxEXPAND); + s7->Add(new wxStaticText(this, wxID_ANY, _("&Top")), 0, wxALIGN_CENTER_VERTICAL); + s7->Add(top, 0, wxEXPAND); + s7->Add(new wxStaticText(this, wxID_ANY, _("&Width")), 0, wxALIGN_CENTER_VERTICAL); + s7->Add(width, 0, wxEXPAND); + s7->Add(new wxStaticText(this, wxID_ANY, _("&Height")), 0, wxALIGN_CENTER_VERTICAL); + s7->Add(height, 0, wxEXPAND); + s6->Add(s7, 1, wxEXPAND | wxALL, 4); + s5->Add(s6, 0, wxEXPAND | wxALL, 4); + s5->AddStretchSpacer(1); + wxSizer* s8 = zoom_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Zoom")); + s8->Add(fix_aspect, 0, wxEXPAND | wxALL & ~wxBOTTOM, 4); + wxSizer* s9 = zoom_fixed = new wxFlexGridSizer(0, 3, 4, 5); + s9->Add(new wxStaticText(this, wxID_ANY, _("&Zoom")), 0, wxALIGN_CENTER_VERTICAL); + s9->Add(zoom, 0, wxEXPAND); + s9->Add(new wxStaticText(this, wxID_ANY, _("%")), 0, wxALIGN_CENTER_VERTICAL); + s8->Add(s9, 0, wxEXPAND | wxALL, 4); + wxSizer* sA = zoom_free = new wxFlexGridSizer(0, 3, 4, 5); + sA->Add(new wxStaticText(this, wxID_ANY, _("Zoom &X")), 0, wxALIGN_CENTER_VERTICAL); + sA->Add(zoom_x, 0, wxEXPAND); + sA->Add(new wxStaticText(this, wxID_ANY, _("%")), 0, wxALIGN_CENTER_VERTICAL); + sA->Add(new wxStaticText(this, wxID_ANY, _("Zoom &Y")), 0, wxALIGN_CENTER_VERTICAL); + sA->Add(zoom_y, 0, wxEXPAND); + sA->Add(new wxStaticText(this, wxID_ANY, _("%")), 0, wxALIGN_CENTER_VERTICAL); + s8->Add(sA, 0, wxEXPAND | wxALL, 4); + s5->Add(s8, 0, wxEXPAND | wxALL, 4); + s5->AddStretchSpacer(1); + wxSizer* sB = new wxStaticBoxSizer(wxVERTICAL, this, _("Filter")); + sB->Add(sharpen, 0, wxEXPAND | wxALL & ~wxBOTTOM, 4); + sB->Add(sharpen_amount, 0, wxEXPAND | wxALL, 4); + s5->Add(sB, 0, wxEXPAND | wxALL, 4); + s5->AddStretchSpacer(1); + s->Add(s5, 0, wxEXPAND); + s->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 8); + s->SetSizeHints(this); + SetSizer(s); + updateControls(); +} + +void ImageSliceWindow::onOk(wxCommandEvent&) { + EndModal(wxID_OK); +} + +Image ImageSliceWindow::getImage() const { + return slice.getSlice(); +} + +// ----------------------------------------------------------------------------- : ImageSliceWindow : Controls + +void ImageSliceWindow::onChangeSize(wxCommandEvent&) { + int sel = size->GetSelection(); + if (sel == 0) { + // original size + slice.selection.width = slice.target_size.GetWidth(); + slice.selection.height = slice.target_size.GetHeight(); + slice.aspect_fixed = true; + onUpdateFromControl(); + } else if (sel == 1) { + // size to fit + slice.selection.x = slice.selection.y = 0; + slice.selection.width = slice.source.GetWidth(); + slice.selection.height = slice.source.GetHeight(); + slice.aspect_fixed = true; + onUpdateFromControl(); + } else if (sel == 2) { + // force to fit + slice.selection.x = slice.selection.y = 0; + slice.selection.width = slice.source.GetWidth(); + slice.selection.height = slice.source.GetHeight(); + slice.aspect_fixed = false; + onUpdateFromControl(); + } +} + +void ImageSliceWindow::onChangeLeft(wxCommandEvent&) { + slice.selection.x = left->GetValue(); + onUpdateFromControl(); +} +void ImageSliceWindow::onChangeTop(wxCommandEvent&) { + slice.selection.y = top->GetValue(); + onUpdateFromControl(); +} +void ImageSliceWindow::onChangeWidth(wxCommandEvent&) { + slice.selection.width = width->GetValue(); + onUpdateFromControl(); +} +void ImageSliceWindow::onChangeHeight(wxCommandEvent&) { + slice.selection.height = height->GetValue(); + onUpdateFromControl(); +} + +void ImageSliceWindow::onChangeFixAspect(wxCommandEvent&) { + slice.aspect_fixed = fix_aspect->GetValue(); + onUpdateFromControl(); +} + +void ImageSliceWindow::onChangeZoom(wxSpinEvent&) { + slice.zoomX(zoom->GetValue() / 100.0); + slice.zoomY(zoom->GetValue() / 100.0); + onUpdateFromControl(); +} +void ImageSliceWindow::onChangeZoomX(wxSpinEvent&) { + slice.zoomX(zoom_x->GetValue() / 100.0); + onUpdateFromControl(); +} +void ImageSliceWindow::onChangeZoomY(wxSpinEvent&) { + slice.zoomY(zoom_x->GetValue() / 100.0); + onUpdateFromControl(); +} + +void ImageSliceWindow::onChangeSharpen(wxCommandEvent&) { + slice.sharpen = sharpen->GetValue(); + onUpdateFromControl(); +} +void ImageSliceWindow::onChangeSharpenAmount(wxScrollEvent&) { + slice.sharpen_amount = sharpen_amount->GetValue(); + onUpdateFromControl(); +} + +// ----------------------------------------------------------------------------- : ImageSliceWindow : Updates + +void ImageSliceWindow::onSelectionUpdate() { + slice.constrain(); +// //preview.update(); + selector->update(); + updateControls(); +} + +void ImageSliceWindow::onUpdateFromControl() { + slice.constrain(); + preview->update(); + selector->update(); + updateControls(); +} + +void ImageSliceWindow::updateControls() { + if (slice.selection.width == slice.target_size.GetWidth() && slice.selection.height == slice.target_size.GetHeight()) { + size->SetSelection(0); // original size + } else if (slice.selection.x == 0 && slice.selection.width == slice.source.GetWidth() && + slice.selection.y == 0 && slice.selection.height == slice.source.GetHeight()) { + size->SetSelection(2); // force to fit + } else if (slice.selection.width <= slice.source.GetWidth() && + slice.selection.height <= slice.source.GetHeight() && + ( (slice.selection.x == 0 && slice.selection.width == slice.source.GetWidth()) + ||(slice.selection.y == 0 && slice.selection.height == slice.source.GetHeight()))) { + size->SetSelection(1); // size to fit + } else { + size->SetSelection(3); // custom size + } + left ->SetValue(slice.selection.x); + top ->SetValue(slice.selection.y); + width ->SetValue(slice.selection.width); + height ->SetValue(slice.selection.height); + fix_aspect->SetValue(slice.aspect_fixed); + if (slice.aspect_fixed) { + zoom->SetValue(slice.zoomX() * 100); + if (zoom_x->IsShown()) { + zoom_sizer->Show(zoom_fixed, true); + zoom_sizer->Show(zoom_free, false); + Layout(); + } + } else { + zoom_x->SetValue(slice.zoomX() * 100); + zoom_y->SetValue(slice.zoomY() * 100); + if (zoom->IsShown()) { + zoom_sizer->Show(zoom_fixed, false); + zoom_sizer->Show(zoom_free, true); + Layout(); + } + } + sharpen ->SetValue(slice.sharpen); + sharpen_amount->SetValue(slice.sharpen_amount); + sharpen_amount->Enable(slice.sharpen); +} + +// ----------------------------------------------------------------------------- : ImageSliceWindow : Event table + +BEGIN_EVENT_TABLE(ImageSliceWindow, wxDialog) + EVT_BUTTON (wxID_OK, ImageSliceWindow::onOk) + EVT_RADIOBOX (ID_SIZE, ImageSliceWindow::onChangeSize) + EVT_TEXT (ID_LEFT, ImageSliceWindow::onChangeLeft) + EVT_TEXT (ID_TOP, ImageSliceWindow::onChangeTop) + EVT_TEXT (ID_WIDTH, ImageSliceWindow::onChangeWidth) + EVT_TEXT (ID_HEIGHT, ImageSliceWindow::onChangeHeight) + EVT_CHECKBOX (ID_FIX_ASPECT, ImageSliceWindow::onChangeFixAspect) + EVT_SPINCTRL (ID_ZOOM, ImageSliceWindow::onChangeZoom) + EVT_SPINCTRL (ID_ZOOM_X, ImageSliceWindow::onChangeZoomX) + EVT_SPINCTRL (ID_ZOOM_Y, ImageSliceWindow::onChangeZoomY) + EVT_CHECKBOX (ID_SHARPEN, ImageSliceWindow::onChangeSharpen) + EVT_COMMAND_SCROLL (ID_SHARPEN_AMOUNT, ImageSliceWindow::onChangeSharpenAmount) +// EVT_SIZE ( ImageSliceWindow::onSize) +END_EVENT_TABLE () + + + + + +// ----------------------------------------------------------------------------- : ImageSlicePreview + +ImageSlicePreview::ImageSlicePreview(Window* parent, int id, ImageSlice& slice) + : wxControl(parent, id) + , slice(slice) + , mouse_down(false) +{} + +void ImageSlicePreview::update() { + bitmap = wxNullBitmap; + Refresh(false); +} + +wxSize ImageSlicePreview::DoGetBestSize() const { + // We know the client size we want, calculate the size that goes with that + wxSize ws = GetSize(), cs = GetClientSize(); + return slice.target_size + ws - cs; +} + +void ImageSlicePreview::onPaint(wxPaintEvent&) { + wxPaintDC dc(this); + dc.BeginDrawing(); + draw(dc); + dc.EndDrawing(); +} +void ImageSlicePreview::draw(DC& dc) { + if (!bitmap.Ok()) { + bitmap = Bitmap(slice.getSlice()); + } + if (bitmap.Ok()) { + dc.DrawBitmap(bitmap, 0, 0); + } +} + +void ImageSlicePreview::onLeftDown(wxMouseEvent& ev) { + mouseX = ev.GetX(); + mouseY = ev.GetY(); + start_selection = slice.selection; + mouse_down = true; + CaptureMouse(); + SetCursor(wxCURSOR_SIZING); +} +void ImageSlicePreview::onLeftUp(wxMouseEvent&v) { + mouse_down = false; + if (HasCapture()) ReleaseMouse(); + SetCursor(wxNullCursor); +} +void ImageSlicePreview::onMotion(wxMouseEvent& ev) { + if (mouse_down) { + // drag the image + slice.selection.x = start_selection.x + (mouseX - ev.GetX()) / slice.zoomX(); + slice.selection.y = start_selection.x + (mouseY - ev.GetY()) / slice.zoomY(); +// parent->onSelectionUpdate(); + } +} + +BEGIN_EVENT_TABLE(ImageSlicePreview, wxControl) + EVT_PAINT (ImageSlicePreview::onPaint) + EVT_LEFT_DOWN (ImageSlicePreview::onLeftDown) + EVT_LEFT_UP (ImageSlicePreview::onLeftUp) + EVT_MOTION (ImageSlicePreview::onMotion) +END_EVENT_TABLE () + + + + + +// ----------------------------------------------------------------------------- : ImageSliceSelector + +ImageSliceSelector::ImageSliceSelector(Window* parent, int id, ImageSlice& slice) + : wxControl(parent, id) + , slice(slice) + , mouse_down(false) +{} + +void ImageSliceSelector::update() { + Refresh(false); +} + +void ImageSliceSelector::onSize(wxSizeEvent&) { + bitmap = wxNullBitmap; + Refresh(false); +} + +// ----------------------------------------------------------------------------- : ImageSliceSelector : Drawing + +void ImageSliceSelector::onPaint(wxPaintEvent&) { + wxBufferedPaintDC dc(this); + dc.BeginDrawing(); + draw(dc); + dc.EndDrawing(); +} +void ImageSliceSelector::draw(DC& dc) { + if (!bitmap.Ok()) createBitmap(); + if (!bitmap.Ok()) return; + // Selected region + wxSize s = GetClientSize(); + int left = slice.selection.x * scaleX + border; + int top = slice.selection.y * scaleY + border; + int width = slice.selection.width * scaleX; + int height = slice.selection.height * scaleY; + // background + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(Color(128,128,128)); + dc.DrawRectangle(0, 0, s.GetWidth(), s.GetHeight()); + // bitmap : unselected + dc.DrawBitmap(bitmap_no_sel, border, border); + // draw selected part ungreyed over it + { + wxMemoryDC mdc; + mdc.SelectObject(bitmap); + dc.Blit(left, top, width, height, &mdc, left - border, top - border); + mdc.SelectObject(wxNullBitmap); + } + // border around source + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetPen(*wxBLACK_PEN); + dc.DrawRectangle(left - 1, top - 1, width + 2, height + 2); + dc.SetPen(Color(64,64,64)); + dc.DrawRectangle(left - 2, top - 2, width + 4, height + 4); + // Draw handles on all sides + dc.SetBrush(Color(0,0,128)); + dc.SetPen(*wxWHITE_PEN); + drawHandle(dc, -1, -1); + drawHandle(dc, -1, +1); + drawHandle(dc, +1, -1); + drawHandle(dc, +1, +1); + if (!slice.aspect_fixed) { + drawHandle(dc, -1, 0); + drawHandle(dc, 0, -1); + drawHandle(dc, 0, +1); + drawHandle(dc, +1, 0); + } +} +void ImageSliceSelector::drawHandle(DC& dc, int dx, int dy) { + wxPoint p = handlePos(dx, dy); + dc.DrawRectangle(p.x - 3 + 4 * dx, p.y - 3 + 4 * dy, 6, 6); +} + +int blur_pixel(Byte* in, int x, int y, int width, int height) { + return (2 * ( in[0]) + // center + (x == 0 ? in[0] : in[-3]) + // left + (y == 0 ? in[0] : in[-3*width]) + // up + (x == width - 1 ? in[0] : in[3]) + // right + (y == height - 1 ? in[0] : in[3*width]) // down + ) / 6; +} + +void blur_image(const Image& img_in, Image& img_out) { + int width = img_in.GetWidth(), height = img_in.GetHeight(); + assert(img_out.GetWidth() == width && img_out.GetHeight() == height); + Byte* in = img_in.GetData(), *out = img_out.GetData(); + for (int y = 0 ; y < height ; ++y) { + for (int x = 0 ; x < width ; ++x) { + out[0] = blur_pixel(in + 0, x, y, width, height); + out[1] = blur_pixel(in + 1, x, y, width, height); + out[2] = blur_pixel(in + 2, x, y, width, height); + in += 3; out += 3; + } + } +} +void desaturate_image(Image& img) { + int width = img.GetWidth(), height = img.GetHeight(); + Byte* in = img.GetData(); + for (int y = 0 ; y < height ; ++y) { + for (int x = 0 ; x < width ; ++x) { + int r = in[0], g = in[1], b = in[2]; + // desaturate + in[0] = (r + g + b + 5 * r + 255) / 9; + in[1] = (r + g + b + 5 * g + 255) / 9; + in[2] = (r + g + b + 5 * b + 255) / 9; + in += 3; + } + } +} + +void ImageSliceSelector::createBitmap() { + // create image, resampled to fit in control + wxSize s = GetClientSize(); + int width = s.GetWidth() - 2*border, height = s.GetHeight() - 2*border; + Image img(width, height, false); + resample(slice.source, img); + bitmap = Bitmap(img); + scaleX = (double)width / slice.source.GetWidth(); + scaleY = (double)height / slice.source.GetHeight(); + // Initialize bitmap_no_sel to be the same bitmap, only with a faded color and blurred + Image img_no_sel(width, height, false); + blur_image(img, img_no_sel); + blur_image(img_no_sel, img_no_sel); + blur_image(img_no_sel, img_no_sel); + desaturate_image(img_no_sel); + bitmap_no_sel = Bitmap(img_no_sel); +} + +// ----------------------------------------------------------------------------- : ImageSliceSelector : Mouse + +void ImageSliceSelector::onLeftDown(wxMouseEvent& ev) { + mouseX = ev.GetX(); + mouseY = ev.GetY(); + start_selection = slice.selection; + mouse_down = true; + onAnyHandle(ev, &dragX, &dragY); + if (slice.aspect_fixed && (dragX == 0 || dragY == 0)) { + dragX = dragY = 0; // only drag corners if aspect fixed + } + if (dragX == 0 && dragY == 0) { + SetCursor(wxCURSOR_SIZING); + } + CaptureMouse(); +} +void ImageSliceSelector::onLeftUp(wxMouseEvent&) { + mouse_down = false; + if (HasCapture()) ReleaseMouse(); + SetCursor(wxNullCursor); +} + +void ImageSliceSelector::onMotion(wxMouseEvent& ev) { + if (mouse_down) { + double deltaX = (ev.GetX() - mouseX) / scaleX; + double deltaY = (ev.GetY() - mouseY) / scaleY; + // are we on a handle? + if (dragX == 0 && dragY == 0) { + // dragging entire selection + slice.selection.x = start_selection.x + deltaX; + slice.selection.y = start_selection.y + deltaY; + } else { + // fix aspect ratio + if (slice.aspect_fixed) { + if (abs(deltaX * slice.target_size.GetWidth()) > + abs(deltaY * slice.target_size.GetHeight())) { + deltaY = dragX * dragY * deltaX * slice.target_size.GetWidth() / slice.target_size.GetHeight(); + } else { + deltaX = dragX * dragY * deltaY * slice.target_size.GetHeight() / slice.target_size.GetWidth(); + } + } + // move + slice.selection.x = start_selection.x + deltaX * (1 - dragX) / 2; + slice.selection.y = start_selection.y + deltaY * (1 - dragY) / 2; + slice.selection.width = start_selection.width + deltaX * dragX; + slice.selection.height = start_selection.height + deltaY * dragY; + } + + // Refresh +// parent->onSelectionUpdate(); + } else { + int dx, dy; + if (onAnyHandle(ev, &dx, &dy)) { + // what cursor to use? + if (dx == dy) SetCursor(wxCURSOR_SIZENWSE); + else if (dx == -dy) SetCursor(wxCURSOR_SIZENESW); + else if (dx == 0) SetCursor(wxCURSOR_SIZENS); + else if (dy == 0) SetCursor(wxCURSOR_SIZEWE); + } else { + SetCursor(*wxSTANDARD_CURSOR); + } + } +} + +// ----------------------------------------------------------------------------- : ImageSliceSelector : handles + +bool ImageSliceSelector::onHandle(const wxMouseEvent& ev, int dx, int dy) const { + wxPoint p = handlePos(dx, dy); + p.x = p.x - 3 + 4 * dx; + p.y = p.y - 3 + 4 * dy; + return ev.GetX() >= p.x && ev.GetX() < p.x + 6 && + ev.GetY() >= p.y && ev.GetY() < p.y + 6; +} +bool ImageSliceSelector::onAnyHandle(const wxMouseEvent& ev, int* dxOut, int* dyOut) const { + for (int dx = -1 ; dx <= 1 ; ++dx) { + for (int dy = -1 ; dy <= 1 ; ++dy) { + if ((dx != 0 || dy != 0) && onHandle(ev, dx, dy)) { // (0,0) == center, not a handle + *dxOut = dx; + *dyOut = dy; + return true; + } + } + } + *dxOut = *dyOut = 0; + return false; +} +wxPoint ImageSliceSelector::handlePos(int dx, int dy) const { + return wxPoint( + scaleX * (slice.selection.x + ((dx + 1) * slice.selection.width) * 0.5) + border, + scaleY * (slice.selection.y + ((dy + 1) * slice.selection.height) * 0.5) + border + ); +} + +// ----------------------------------------------------------------------------- : ImageSliceSelector : Event table + +BEGIN_EVENT_TABLE(ImageSliceSelector, wxControl) + EVT_PAINT (ImageSliceSelector::onPaint) + EVT_LEFT_DOWN (ImageSliceSelector::onLeftDown) + EVT_LEFT_UP (ImageSliceSelector::onLeftUp) + EVT_MOTION (ImageSliceSelector::onMotion) + EVT_SIZE (ImageSliceSelector::onSize) +END_EVENT_TABLE () diff --git a/src/gui/image_slice_window.hpp b/src/gui/image_slice_window.hpp new file mode 100644 index 00000000..95a6e672 --- /dev/null +++ b/src/gui/image_slice_window.hpp @@ -0,0 +1,183 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_GUI_IMAGE_SLICE_WINDOW +#define HEADER_GUI_IMAGE_SLICE_WINDOW + +// ----------------------------------------------------------------------------- : Includes + +#include + +class ImageSlicePreview; +class ImageSliceSelector; +class wxSpinEvent; + +// ----------------------------------------------------------------------------- : ImageSlice + +/// A slice of an image, i.e. a selected rectangle +class ImageSlice { + public: + ImageSlice(const Image& source, const wxSize& target_size); + + Image source; ///< The source image + wxSize target_size; ///< Size of the target image + Color background; ///< Color for areas outside the source image + wxRect selection; ///< Area to slect from source + bool allow_outside; + bool aspect_fixed; ///< Aspect ratio lock? + // Filters + bool sharpen; + int sharpen_amount; + + /// Enforce relations between values + void constrain(); + /// Get the sliced image + Image getSlice() const; + + // Zoom factor + inline double zoomX() const { return target_size.GetWidth() / (double)selection.width; } + inline double zoomY() const { return target_size.GetHeight() / (double)selection.height; } + inline void zoomX(double zoom) { selection.width = target_size.GetWidth() / zoom; } + inline void zoomY(double zoom) { selection.height = target_size.GetHeight() / zoom; } +}; + + +// ----------------------------------------------------------------------------- : ImageSliceWindow + +/// Dialog for selecting a slice of an image +class ImageSliceWindow : public wxDialog { + public: + ImageSliceWindow(Window* parent, const Image& source, const wxSize& target_size); + + /// Return the sliced image + Image getImage() const; + + // --------------------------------------------------- : Data + private: + // The slice we are extracting + ImageSlice slice; + // Gui items + ImageSlicePreview* preview; + ImageSliceSelector* selector; + wxRadioBox* size; + wxSpinCtrl* top, *left, *width, *height; + wxCheckBox* fix_aspect; + wxSpinCtrl* zoom, *zoom_x, *zoom_y; + wxSizer* zoom_sizer, *zoom_fixed, *zoom_free; + wxCheckBox* sharpen; + wxSlider* sharpen_amount; + + // --------------------------------------------------- : Events + DECLARE_EVENT_TABLE(); + + void onOk (wxCommandEvent&); + + void onSize (wxSizeEvent&); + + void onChangeSize (wxCommandEvent&); + void onChangeLeft (wxCommandEvent&); + void onChangeTop (wxCommandEvent&); + void onChangeWidth (wxCommandEvent&); + void onChangeHeight (wxCommandEvent&); + void onChangeFixAspect (wxCommandEvent&); + void onChangeZoom (wxSpinEvent&); + void onChangeZoomX (wxSpinEvent&); + void onChangeZoomY (wxSpinEvent&); + void onChangeSharpen (wxCommandEvent&); + void onChangeSharpenAmount(wxScrollEvent&); + + // --------------------------------------------------- : Updating + + // Something changed in the selector control, update controls and selection displays + void onSelectionUpdate(); + // The manual controls were changed + void onUpdateFromControl(); + // Update the values in the controls + void updateControls(); +}; + + +// ----------------------------------------------------------------------------- : ImageSlicePreview + +/// A preview of the sliced image +class ImageSlicePreview : public wxControl { + public: + ImageSlicePreview(Window* parent, int id, ImageSlice& slice); + + /// Notify that the slice was updated + void update(); + + // --------------------------------------------------- : Data + private: + Bitmap bitmap; + ImageSlice& slice; + + bool mouse_down; + int mouseX, mouseY; ///< starting mouse position + wxRect start_selection; ///< selection in slice at start of dragging + + // --------------------------------------------------- : Events + DECLARE_EVENT_TABLE(); + + wxSize DoGetBestSize() const; + + void onLeftDown(wxMouseEvent&); + void onLeftUp (wxMouseEvent&); + void onMotion (wxMouseEvent&); + + void onPaint(wxPaintEvent&); + void draw(DC& dc); +}; + +// ----------------------------------------------------------------------------- : ImageSliceSelector + +// A overview of the slicing of the image, allows to select the sliced area +class ImageSliceSelector : public wxControl { + public: + ImageSliceSelector(Window* parent, int id, ImageSlice& slice); + + /// Notify that the slice was updated + void update(); + + // --------------------------------------------------- : Data + private: + ImageSlice& slice; + Bitmap bitmap, bitmap_no_sel; ///< Bitmaps showing selection + + bool mouse_down; + int mouseX, mouseY; ///< starting mouse position + int dragX, dragY; ///< corner that is being dragged + wxRect start_selection; ///< selection in slice at start of dragging + double scaleX, scaleY; ///< Amount the source image is scaled to fit in this control + static const int border = 8; + + // --------------------------------------------------- : Events + DECLARE_EVENT_TABLE(); + + void onLeftDown(wxMouseEvent&); + void onLeftUp (wxMouseEvent&); + void onMotion (wxMouseEvent&); + + void onPaint(wxPaintEvent&); + void onSize(wxSizeEvent&); + + // Is the mouse on a (scale) handle? + bool onHandle(const wxMouseEvent& ev, int dx, int dy) const; + // Is the mouse on any handle? + bool onAnyHandle(const wxMouseEvent& ev, int* dxOut, int* dyOut) const; + // Return the position of a handle, dx,dy in {-1,0,1} + wxPoint handlePos(int dx, int dy) const; + + void draw(DC& dc); + // Draw a handle, dx and dy indicate the side, can be {-1,0,1} + void drawHandle(DC& dc, int dx, int dy); + void createBitmap(); + + +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/value/editor.hpp b/src/gui/value/editor.hpp index 0bb37350..50066698 100644 --- a/src/gui/value/editor.hpp +++ b/src/gui/value/editor.hpp @@ -69,7 +69,7 @@ class ValueEditor { // Deletes the selection from this field editor, cut = copy + delete, returns success virtual bool doDelete() { return false; } // Cuts the selection from this field editor - bool doCut() { return doCopy() && doDelete(); } + bool doCut() { return doCopy() && doDelete(); } /// Initiate pasting in this field editor, /** should again check if pasting is possible and fail silently if not, returns success */ virtual bool doPaste() { return false; } diff --git a/src/gui/value/image.cpp b/src/gui/value/image.cpp index a67d209c..b4a151d1 100644 --- a/src/gui/value/image.cpp +++ b/src/gui/value/image.cpp @@ -7,7 +7,69 @@ // ----------------------------------------------------------------------------- : Includes #include +#include +#include +#include +#include -// ----------------------------------------------------------------------------- : +// ----------------------------------------------------------------------------- : ImageValueEditor IMPLEMENT_VALUE_EDITOR(Image) {} + +void ImageValueEditor::onLeftDClick(const RealPoint&, wxMouseEvent&) { + String filename = wxFileSelector(_("Open image file"), _(""), _(""), _(""), + _("All images|*.bmp;*.jpg;*.png;*.gif|Windows bitmaps (*.bmp)|*.bmp|JPEG images (*.jpg;*.jpeg)|*.jpg;*.jpeg|PNG images (*.png)|*.png|GIF images (*.gif)|*.gif|TIFF images (*.tif;*.tiff)|*.tif;*.tiff"), + wxOPEN); + if (filename.empty()) { + return; + } else { + sliceImage(wxImage(filename)); + } +} + +void ImageValueEditor::sliceImage(const Image& image) { + if (!image.Ok()) return; + // slice + ImageSliceWindow s(wxGetTopLevelParent(&editor()), image, style().getSize()); + if (s.ShowModal() == wxID_OK) { + // store the image into the set + FileName new_image_file = getSet().newFileName(field().name,_("")); // a new unique name in the package + s.getImage().SaveFile(getSet().nameOut(new_image_file), wxBITMAP_TYPE_JPEG); + getSet().actions.add(value_action(valueP(), new_image_file)); + } +} + +// ----------------------------------------------------------------------------- : Clipboard + +bool ImageValueEditor::canCopy() const { + return !value().filename.empty(); +} + +bool ImageValueEditor::canPaste() const { + return wxTheClipboard->IsSupported(wxDF_BITMAP) && + !wxTheClipboard->IsSupported(CardDataObject::format); // we don't want to (accidentally) paste card images +} + +bool ImageValueEditor::doCopy() { + // load image + InputStreamP image_file = getSet().openIn(value().filename); + Image image; + if (!image.LoadFile(*image_file)) return false; + // set data + if (!wxTheClipboard->Open()) return false; + bool ok = wxTheClipboard->SetData(new wxBitmapDataObject(image)); + wxTheClipboard->Close(); + return ok; +} + +bool ImageValueEditor::doPaste() { + // get bitmap + if (!wxTheClipboard->Open()) return false; + wxBitmapDataObject data; + bool ok = wxTheClipboard->GetData(data); + wxTheClipboard->Close(); + if (!ok) return false; + // slice + sliceImage(data.GetBitmap().ConvertToImage()); + return true; +} diff --git a/src/gui/value/image.hpp b/src/gui/value/image.hpp index e856f54c..22541d29 100644 --- a/src/gui/value/image.hpp +++ b/src/gui/value/image.hpp @@ -19,6 +19,20 @@ class ImageValueEditor : public ImageValueViewer, public ValueEditor { public: DECLARE_VALUE_EDITOR(Image); + + virtual void onLeftDClick(const RealPoint&, wxMouseEvent&); + + // --------------------------------------------------- : Clipboard + + virtual bool canCopy() const; + virtual bool canCut() const { return false; } + virtual bool canPaste() const; + virtual bool doCopy(); + virtual bool doPaste(); + + private: + // Open the image slice window showing the give image + void sliceImage(const Image&); }; // ----------------------------------------------------------------------------- : EOF