diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index d10669baa8..61cb0dd19e 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -259,4 +259,5 @@ add_library(app-lib webserver.cpp widget_loader.cpp xml_document.cpp - xml_exception.cpp) + xml_exception.cpp + zoom.cpp) diff --git a/src/app/commands/cmd_move_mask.cpp b/src/app/commands/cmd_move_mask.cpp index 2e4bb8c78d..cfd0ea983f 100644 --- a/src/app/commands/cmd_move_mask.cpp +++ b/src/app/commands/cmd_move_mask.cpp @@ -99,13 +99,13 @@ void MoveMaskCommand::onExecute(Context* context) pixels = gridBounds.h; break; case ZoomedPixel: - pixels = 1 << current_editor->zoom(); + pixels = current_editor->zoom().apply(1); break; case ZoomedTileWidth: - pixels = gridBounds.w << current_editor->zoom(); + pixels = current_editor->zoom().apply(gridBounds.w); break; case ZoomedTileHeight: - pixels = gridBounds.h << current_editor->zoom(); + pixels = current_editor->zoom().apply(gridBounds.h); break; case ViewportWidth: pixels = vp.h; diff --git a/src/app/commands/cmd_preview.cpp b/src/app/commands/cmd_preview.cpp index ab9a123a31..0531584fb8 100644 --- a/src/app/commands/cmd_preview.cpp +++ b/src/app/commands/cmd_preview.cpp @@ -63,7 +63,8 @@ class PreviewWindow : public Window { , m_pal(m_sprite->getPalette(editor->frame())) , m_index_bg_color(-1) , m_doublebuf(Image::create(IMAGE_RGB, ui::display_w(), ui::display_h())) - , m_doublesur(she::instance()->createRgbaSurface(ui::display_w(), ui::display_h())) { + , m_doublesur(she::instance()->createRgbaSurface(ui::display_w(), ui::display_h())) + , m_zoom(editor->zoom()) { // Do not use DocumentWriter (do not lock the document) because we // will call other sub-commands (e.g. previous frame, next frame, // etc.). @@ -83,7 +84,6 @@ class PreviewWindow : public Window { m_oldMousePos = ui::get_mouse_position(); m_pos.x = -scroll.x + vp.x + editor->offsetX(); m_pos.y = -scroll.y + vp.y + editor->offsetY(); - m_zoom = editor->zoom(); setFocusStop(true); captureMouse(); @@ -193,14 +193,14 @@ class PreviewWindow : public Window { m_render.reset( renderEngine.renderSprite( 0, 0, m_sprite->width(), m_sprite->height(), - m_editor->frame(), 0, false, false)); + m_editor->frame(), Zoom(1, 1), false, false)); } int x, y, w, h, u, v; - x = m_pos.x + ((m_delta.x >> m_zoom) << m_zoom); - y = m_pos.y + ((m_delta.y >> m_zoom) << m_zoom); - w = (m_sprite->width()<height()<width()); + h = m_zoom.apply(m_sprite->height()); if (m_tiled & TILED_X_AXIS) x = SGN(x) * (ABS(x)%w); if (m_tiled & TILED_Y_AXIS) y = SGN(y) * (ABS(y)%h); @@ -243,7 +243,7 @@ class PreviewWindow : public Window { gfx::Point m_pos; gfx::Point m_oldMousePos; gfx::Point m_delta; - int m_zoom; + Zoom m_zoom; int m_index_bg_color; base::UniquePtr m_render; base::UniquePtr m_doublebuf; diff --git a/src/app/commands/cmd_scroll.cpp b/src/app/commands/cmd_scroll.cpp index 38211b8c8e..596f993523 100644 --- a/src/app/commands/cmd_scroll.cpp +++ b/src/app/commands/cmd_scroll.cpp @@ -117,13 +117,13 @@ void ScrollCommand::onExecute(Context* context) pixels = gridBounds.h; break; case ZoomedPixel: - pixels = 1 << current_editor->zoom(); + pixels = current_editor->zoom().apply(1); break; case ZoomedTileWidth: - pixels = gridBounds.w << current_editor->zoom(); + pixels = current_editor->zoom().apply(gridBounds.w); break; case ZoomedTileHeight: - pixels = gridBounds.h << current_editor->zoom(); + pixels = current_editor->zoom().apply(gridBounds.h); break; case ViewportWidth: pixels = vp.h; diff --git a/src/app/commands/cmd_zoom.cpp b/src/app/commands/cmd_zoom.cpp index 2e21758222..a419a3431c 100644 --- a/src/app/commands/cmd_zoom.cpp +++ b/src/app/commands/cmd_zoom.cpp @@ -74,25 +74,23 @@ bool ZoomCommand::onEnabled(Context* context) void ZoomCommand::onExecute(Context* context) { - int zoom = current_editor->zoom(); + Zoom zoom = current_editor->zoom(); switch (m_action) { case In: - if (zoom < 5) - ++zoom; + zoom.in(); break; case Out: - if (zoom > 0) - --zoom; + zoom.out(); break; case Set: switch (m_percentage) { - case 3200: zoom = 5; break; - case 1600: zoom = 4; break; - case 800: zoom = 3; break; - case 400: zoom = 2; break; - case 200: zoom = 1; break; - default: zoom = 0; break; + case 3200: zoom = Zoom(32, 1); break; + case 1600: zoom = Zoom(16, 1); break; + case 800: zoom = Zoom(8, 1); break; + case 400: zoom = Zoom(4, 1); break; + case 200: zoom = Zoom(2, 1); break; + default: zoom = Zoom(1, 1); break; } break; } diff --git a/src/app/commands/filters/filter_manager_impl.cpp b/src/app/commands/filters/filter_manager_impl.cpp index c79ad605b7..e2bd583018 100644 --- a/src/app/commands/filters/filter_manager_impl.cpp +++ b/src/app/commands/filters/filter_manager_impl.cpp @@ -257,8 +257,8 @@ void FilterManagerImpl::flush() m_x+m_offset_x, m_y+m_offset_y+m_row-1)), gfx::Size( - (m_w << editor->zoom()), - (1 << editor->zoom()))); + editor->zoom().apply(m_w), + editor->zoom().apply(1))); gfx::Region reg1(rect); gfx::Region reg2; diff --git a/src/app/thumbnail_generator.cpp b/src/app/thumbnail_generator.cpp index c0ac820f85..e0fe1b6332 100644 --- a/src/app/thumbnail_generator.cpp +++ b/src/app/thumbnail_generator.cpp @@ -85,7 +85,7 @@ class ThumbnailGenerator::Worker { base::UniquePtr image(renderEngine.renderSprite( 0, 0, sprite->width(), sprite->height(), - FrameNumber(0), 0, true, false)); + FrameNumber(0), Zoom(1, 1), true, false)); // Calculate the thumbnail size int thumb_w = MAX_THUMBNAIL_SIZE * image->width() / MAX(image->width(), image->height()); diff --git a/src/app/tools/tool_loop.h b/src/app/tools/tool_loop.h index e0b5cb347e..eee2d6c3b2 100644 --- a/src/app/tools/tool_loop.h +++ b/src/app/tools/tool_loop.h @@ -22,6 +22,7 @@ #include "app/settings/selection_mode.h" #include "app/tools/trace_policy.h" +#include "app/zoom.h" #include "doc/frame_number.h" #include "filters/tiled_mode.h" #include "gfx/point.h" @@ -104,6 +105,9 @@ namespace app { // Gets mask X,Y origin coordinates virtual gfx::Point getMaskOrigin() = 0; + // Returns the zoom + virtual const Zoom& zoom() = 0; + // Return the mouse button which start the tool-loop. It can be used // by some tools that instead of using the primary/secondary color // uses the pressed button for different behavior (like selection diff --git a/src/app/tools/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp index 37bb3b6297..1e8a7b25e0 100644 --- a/src/app/tools/tool_loop_manager.cpp +++ b/src/app/tools/tool_loop_manager.cpp @@ -213,7 +213,7 @@ void ToolLoopManager::doLoopStep(bool last_step) // Calculate the area to be updated in all document observers. Region& dirty_area = m_toolLoop->getDirtyArea(); - calculateDirtyArea(m_toolLoop, points_to_interwine, dirty_area); + calculateDirtyArea(points_to_interwine, dirty_area); if (m_toolLoop->getTracePolicy() == TracePolicyLast) { Region prev_dirty_area = dirty_area; @@ -235,7 +235,7 @@ void ToolLoopManager::snapToGrid(Point& point) m_toolLoop->getDocumentSettings()->snapToGrid(point); } -void ToolLoopManager::calculateDirtyArea(ToolLoop* loop, const Points& points, Region& dirty_area) +void ToolLoopManager::calculateDirtyArea(const Points& points, Region& dirty_area) { dirty_area.clear(); @@ -245,21 +245,21 @@ void ToolLoopManager::calculateDirtyArea(ToolLoop* loop, const Points& points, R // Expand the dirty-area with the pen width Rect r1, r2; - loop->getPointShape()->getModifiedArea(loop, minpt.x, minpt.y, r1); - loop->getPointShape()->getModifiedArea(loop, maxpt.x, maxpt.y, r2); + m_toolLoop->getPointShape()->getModifiedArea(m_toolLoop, minpt.x, minpt.y, r1); + m_toolLoop->getPointShape()->getModifiedArea(m_toolLoop, maxpt.x, maxpt.y, r2); dirty_area.createUnion(dirty_area, Region(r1.createUnion(r2))); } // Apply offset mode - Point offset(loop->getOffset()); + Point offset(m_toolLoop->getOffset()); dirty_area.offset(-offset); // Apply tiled mode - TiledMode tiledMode = loop->getDocumentSettings()->getTiledMode(); + TiledMode tiledMode = m_toolLoop->getDocumentSettings()->getTiledMode(); if (tiledMode != TILED_NONE) { - int w = loop->sprite()->width(); - int h = loop->sprite()->height(); + int w = m_toolLoop->sprite()->width(); + int h = m_toolLoop->sprite()->height(); Region sprite_area(Rect(0, 0, w, h)); Region outside; outside.createSubtraction(dirty_area, sprite_area); @@ -312,6 +312,11 @@ void ToolLoopManager::calculateMinMax(const Points& points, Point& minpt, Point& maxpt.x = MAX(maxpt.x, points[c].x); maxpt.y = MAX(maxpt.y, points[c].y); } + + if (m_toolLoop->zoom().scale() < 1.0) { + maxpt.x += m_toolLoop->zoom().remove(1); + maxpt.y += m_toolLoop->zoom().remove(1); + } } } // namespace tools diff --git a/src/app/tools/tool_loop_manager.h b/src/app/tools/tool_loop_manager.h index d51ab87700..adc8e7519d 100644 --- a/src/app/tools/tool_loop_manager.h +++ b/src/app/tools/tool_loop_manager.h @@ -103,13 +103,13 @@ namespace app { void doLoopStep(bool last_step); void snapToGrid(gfx::Point& point); - static void calculateDirtyArea(ToolLoop* loop, - const Points& points, - gfx::Region& dirty_area); + void calculateDirtyArea( + const Points& points, + gfx::Region& dirty_area); - static void calculateMinMax(const Points& points, - gfx::Point& minpt, - gfx::Point& maxpt); + void calculateMinMax(const Points& points, + gfx::Point& minpt, + gfx::Point& maxpt); ToolLoop* m_toolLoop; Points m_points; diff --git a/src/app/ui/editor/cursor.cpp b/src/app/ui/editor/cursor.cpp index 8fda07062a..abc6f0ce4d 100644 --- a/src/app/ui/editor/cursor.cpp +++ b/src/app/ui/editor/cursor.cpp @@ -55,7 +55,7 @@ namespace app { using namespace ui; // Returns true if the cursor of the editor needs subpixel movement. -#define IS_SUBPIXEL(editor) ((editor)->m_zoom >= 2) +#define IS_SUBPIXEL(editor) ((editor)->m_zoom.scale() >= 4.0) // Maximum quantity of colors to save pixels overlapped by the cursor. #define MAX_SAVED 4096 @@ -514,25 +514,22 @@ static void trace_thickcross_pixels(ui::Graphics* g, Editor* editor, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, }; - gfx::Point out; + gfx::Point out, outpt = editor->editorToScreen(pt); int u, v; - int zoom = editor->zoom(); + int size = editor->zoom().apply(thickness/2); + int size2 = editor->zoom().apply(thickness); + if (size2 == 0) size2 = 1; for (v=0; v<6; v++) { for (u=0; u<6; u++) { - if (cursor_cross[v*6+u]) { - out = editor->editorToScreen(pt); - - out.x += ((u<3) ? - u-((thickness>>1)<>1)<>1)<>1)<sprite()) , m_layer(m_sprite->folder()->getFirstLayer()) , m_frame(FrameNumber(0)) - , m_zoom(0) + , m_zoom(1, 1) , m_cursorThick(0) , m_cursorScreen(0, 0) , m_cursorEditor(0, 0) @@ -324,7 +324,7 @@ void Editor::setEditorScroll(const gfx::Point& scroll, bool blit_valid_rgn) drawBrushPreview(m_cursorScreen); } -void Editor::setEditorZoom(int zoom) +void Editor::setEditorZoom(Zoom zoom) { setZoomAndCenterInMouse(zoom, ui::get_mouse_position(), Editor::kCofiguredZoomBehavior); @@ -338,12 +338,12 @@ void Editor::updateEditor() void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc, int dx, int dy) { // Output information - int source_x = rc.x << m_zoom; - int source_y = rc.y << m_zoom; + int source_x = m_zoom.apply(rc.x); + int source_y = m_zoom.apply(rc.y); int dest_x = dx + m_offset_x + source_x; int dest_y = dy + m_offset_y + source_y; - int width = rc.w << m_zoom; - int height = rc.h << m_zoom; + int width = m_zoom.apply(rc.w); + int height = m_zoom.apply(rc.h); // Clip from graphics/screen const gfx::Rect& clip = g->getClipBounds(); @@ -375,11 +375,11 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc, in dest_y -= source_y; source_y = 0; } - if (source_x+width > (m_sprite->width() << m_zoom)) { - width = (m_sprite->width() << m_zoom) - source_x; + if (source_x+width > m_zoom.apply(m_sprite->width())) { + width = m_zoom.apply(m_sprite->width()) - source_x; } - if (source_y+height > (m_sprite->height() << m_zoom)) { - height = (m_sprite->height() << m_zoom) - source_y; + if (source_y+height > m_zoom.apply(m_sprite->height())) { + height = m_zoom.apply(m_sprite->height()) - source_y; } // Draw the sprite @@ -402,7 +402,7 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc, in // Pre-render decorator. if ((m_flags & kShowDecorators) && m_decorator) { EditorPreRenderImpl preRender(this, rendered, - Point(-source_x, -source_y), m_zoom); + Point(-source_x, -source_y), m_zoom); m_decorator->preRenderDecorator(&preRender); } @@ -423,8 +423,8 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc) gfx::Rect spriteRect( client.x + m_offset_x, client.y + m_offset_y, - (m_sprite->width() << m_zoom), - (m_sprite->height() << m_zoom)); + m_zoom.apply(m_sprite->width()), + m_zoom.apply(m_sprite->height())); gfx::Rect enclosingRect = spriteRect; // Draw the main sprite at the center. @@ -473,7 +473,7 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc) } // Draw the pixel grid - if ((m_zoom > 1) && docSettings->getPixelGridVisible()) { + if ((m_zoom.scale() > 1.0) && docSettings->getPixelGridVisible()) { drawGrid(g, enclosingRect, Rect(0, 0, 1, 1), docSettings->getPixelGridColor()); } @@ -544,10 +544,10 @@ void Editor::drawMask(Graphics* g) CheckedDrawMode checked(g, m_offset_count); for (int c=0; cx1 << m_zoom; - y1 = seg->y1 << m_zoom; - x2 = seg->x2 << m_zoom; - y2 = seg->y2 << m_zoom; + x1 = m_zoom.apply(seg->x1); + y1 = m_zoom.apply(seg->y1); + x2 = m_zoom.apply(seg->x2); + y2 = m_zoom.apply(seg->y2); #if 1 // Bounds inside mask if (!seg->open) @@ -863,8 +863,8 @@ gfx::Point Editor::screenToEditor(const gfx::Point& pt) Point scroll = view->getViewScroll(); return gfx::Point( - (pt.x - vp.x + scroll.x - m_offset_x) >> m_zoom, - (pt.y - vp.y + scroll.y - m_offset_y) >> m_zoom); + m_zoom.remove(pt.x - vp.x + scroll.x - m_offset_x), + m_zoom.remove(pt.y - vp.y + scroll.y - m_offset_y)); } Point Editor::editorToScreen(const gfx::Point& pt) @@ -874,8 +874,8 @@ Point Editor::editorToScreen(const gfx::Point& pt) Point scroll = view->getViewScroll(); return Point( - (vp.x - scroll.x + m_offset_x + (pt.x << m_zoom)), - (vp.y - scroll.y + m_offset_y + (pt.y << m_zoom))); + (vp.x - scroll.x + m_offset_x + m_zoom.apply(pt.x)), + (vp.y - scroll.y + m_offset_y + m_zoom.apply(pt.y))); } Rect Editor::screenToEditor(const Rect& rc) @@ -968,8 +968,8 @@ void Editor::centerInSpritePoint(const gfx::Point& spritePos) hideDrawingCursor(); gfx::Point scroll( - m_offset_x - (vp.w/2) + ((1<>1) + (spritePos.x << m_zoom), - m_offset_y - (vp.h/2) + ((1<>1) + (spritePos.y << m_zoom)); + m_offset_x - (vp.w/2) + m_zoom.apply(1)/2 + m_zoom.apply(spritePos.x), + m_offset_y - (vp.h/2) + m_zoom.apply(1)/2 + m_zoom.apply(spritePos.y)); updateEditor(); setEditorScroll(scroll, false); @@ -1212,8 +1212,8 @@ void Editor::onPreferredSize(PreferredSizeEvent& ev) m_offset_x = std::max(vp.w/2, vp.w - m_sprite->width()/2); m_offset_y = std::max(vp.h/2, vp.h - m_sprite->height()/2); - sz.w = (m_sprite->width() << m_zoom) + m_offset_x*2; - sz.h = (m_sprite->height() << m_zoom) + m_offset_y*2; + sz.w = m_zoom.apply(m_sprite->width()) + m_offset_x*2; + sz.h = m_zoom.apply(m_sprite->height()) + m_offset_y*2; } else { sz.w = 4; @@ -1311,7 +1311,7 @@ bool Editor::isInsideSelection() m_document->mask()->containsPoint(spritePos.x, spritePos.y); } -void Editor::setZoomAndCenterInMouse(int zoom, +void Editor::setZoomAndCenterInMouse(Zoom zoom, const gfx::Point& mousePos, ZoomBehavior zoomBehavior) { View* view = View::getView(this); @@ -1343,8 +1343,8 @@ void Editor::setZoomAndCenterInMouse(int zoom, mid.y = mousePos.y; } - spritePos.x = m_offset_x - (mid.x - vp.x) + ((1<>1) + (spritePos.x << zoom); - spritePos.y = m_offset_y - (mid.y - vp.y) + ((1<>1) + (spritePos.y << zoom); + spritePos.x = m_offset_x - (mid.x - vp.x) + (zoom.apply(1)/2) + zoom.apply(spritePos.x); + spritePos.y = m_offset_y - (mid.y - vp.y) + (zoom.apply(1)/2) + zoom.apply(spritePos.y); if ((m_zoom != zoom) || (m_cursorEditor != mid)) { bool blit_valid_rgn = (m_zoom == zoom); diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index 61ab4a0f66..91acf62110 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -27,16 +27,14 @@ #include "app/ui/editor/editor_observers.h" #include "app/ui/editor/editor_state.h" #include "app/ui/editor/editor_states_history.h" +#include "app/zoom.h" #include "base/connection.h" -#include "gfx/fwd.h" #include "doc/frame_number.h" +#include "gfx/fwd.h" #include "ui/base.h" #include "ui/timer.h" #include "ui/widget.h" -#define MIN_ZOOM 0 -#define MAX_ZOOM 5 - namespace doc { class Sprite; class Layer; @@ -122,18 +120,18 @@ namespace app { void setLayer(const Layer* layer); void setFrame(FrameNumber frame); - int zoom() const { return m_zoom; } + const Zoom& zoom() const { return m_zoom; } int offsetX() const { return m_offset_x; } int offsetY() const { return m_offset_y; } int cursorThick() { return m_cursorThick; } - void setZoom(int zoom) { m_zoom = zoom; } + void setZoom(Zoom zoom) { m_zoom = zoom; } void setOffsetX(int x) { m_offset_x = x; } void setOffsetY(int y) { m_offset_y = y; } void setDefaultScroll(); void setEditorScroll(const gfx::Point& scroll, bool blit_valid_rgn); - void setEditorZoom(int zoom); + void setEditorZoom(Zoom zoom); // Updates the Editor's view. void updateEditor(); @@ -187,7 +185,7 @@ namespace app { // Returns true if the cursor is inside the active mask/selection. bool isInsideSelection(); - void setZoomAndCenterInMouse(int zoom, + void setZoomAndCenterInMouse(Zoom zoom, const gfx::Point& mousePos, ZoomBehavior zoomBehavior); void pasteImage(const Image* image, const gfx::Point& pos); @@ -260,7 +258,7 @@ namespace app { Sprite* m_sprite; // Active sprite in the editor Layer* m_layer; // Active layer in the editor FrameNumber m_frame; // Active frame in the editor - int m_zoom; // Zoom in the editor + Zoom m_zoom; // Zoom in the editor // Drawing cursor int m_cursorThick; diff --git a/src/app/ui/editor/select_box_state.cpp b/src/app/ui/editor/select_box_state.cpp index 1c29630011..c795204f14 100644 --- a/src/app/ui/editor/select_box_state.cpp +++ b/src/app/ui/editor/select_box_state.cpp @@ -179,10 +179,10 @@ void SelectBoxState::preRenderDecorator(EditorPreRender* render) void SelectBoxState::postRenderDecorator(EditorPostRender* render) { Editor* editor = render->getEditor(); - int zoom = editor->zoom(); + Zoom zoom = editor->zoom(); gfx::Rect vp = View::getView(editor)->getViewportBounds(); - vp.w += 1<screenToEditor(vp); // Paint a grid generated by the box diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index 96659606d7..e6c439b9f9 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -360,10 +360,20 @@ bool StandbyState::onMouseWheel(Editor* editor, MouseMessage* msg) case WHEEL_ZOOM: { MouseMessage* mouseMsg = static_cast(msg); - int zoom = MID(MIN_ZOOM, editor->zoom()-dz, MAX_ZOOM); - if (editor->zoom() != zoom) + Zoom zoom = editor->zoom(); + if (dz < 0) { + while (dz++ < 0) + zoom.in(); + } + else { + while (dz-- > 0) + zoom.out(); + } + + if (editor->zoom() != zoom) { editor->setZoomAndCenterInMouse(zoom, mouseMsg->position(), Editor::kDontCenterOnZoom); + } break; } diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index 6449a11791..19daa9dbcc 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -223,6 +223,7 @@ class ToolLoopImpl : public tools::ToolLoop, bool useMask() override { return m_useMask; } Mask* getMask() override { return m_mask; } gfx::Point getMaskOrigin() override { return m_maskOrigin; } + const Zoom& zoom() override { return m_editor->zoom(); } ToolLoop::Button getMouseButton() override { return m_button; } int getPrimaryColor() override { return m_primary_color; } void setPrimaryColor(int color) override { m_primary_color = color; } diff --git a/src/app/ui/editor/transform_handles.cpp b/src/app/ui/editor/transform_handles.cpp index 7c130c0f5c..ce578e8efc 100644 --- a/src/app/ui/editor/transform_handles.cpp +++ b/src/app/ui/editor/transform_handles.cpp @@ -196,8 +196,8 @@ gfx::Rect TransformHandles::getPivotHandleBounds(Editor* editor, she::Surface* part = theme->get_part(PART_PIVOT_HANDLE); gfx::Point screenPivotPos = editor->editorToScreen(transform.pivot()); - screenPivotPos.x += (1 << editor->zoom()) / 2; - screenPivotPos.y += (1 << editor->zoom()) / 2; + screenPivotPos.x += editor->zoom().apply(1) / 2; + screenPivotPos.y += editor->zoom().apply(1) / 2; return gfx::Rect( screenPivotPos.x-part->width()/2, diff --git a/src/app/ui/editor/zooming_state.cpp b/src/app/ui/editor/zooming_state.cpp index 87c81e7d3e..f3647029ad 100644 --- a/src/app/ui/editor/zooming_state.cpp +++ b/src/app/ui/editor/zooming_state.cpp @@ -53,12 +53,12 @@ bool ZoomingState::onMouseDown(Editor* editor, MouseMessage* msg) bool ZoomingState::onMouseUp(Editor* editor, MouseMessage* msg) { - int zoom = editor->zoom(); + Zoom zoom = editor->zoom(); - if (msg->left() && zoom < 5) - ++zoom; - else if (msg->right() && zoom > 0) - --zoom; + if (msg->left()) + zoom.in(); + else if (msg->right()) + zoom.out(); editor->setZoomAndCenterInMouse(zoom, msg->position(), Editor::kCofiguredZoomBehavior); diff --git a/src/app/ui/mini_editor.cpp b/src/app/ui/mini_editor.cpp index 984e94b2c5..ffef1a0456 100644 --- a/src/app/ui/mini_editor.cpp +++ b/src/app/ui/mini_editor.cpp @@ -245,7 +245,7 @@ void MiniEditorWindow::updateUsingEditor(Editor* editor) addChild(m_docView); miniEditor = m_docView->getEditor(); - miniEditor->setZoom(0); + miniEditor->setZoom(Zoom(1, 1)); miniEditor->setState(EditorStatePtr(new EditorState)); layout(); } diff --git a/src/app/util/render.cpp b/src/app/util/render.cpp index 4d3506f0f0..34c76d6ff2 100644 --- a/src/app/util/render.cpp +++ b/src/app/util/render.cpp @@ -115,9 +115,8 @@ class BlenderHelper }; template -static void merge_zoomed_image(Image* dst, const Image* src, const Palette* pal, - int x, int y, int opacity, - int blend_mode, int zoom) +static void merge_zoomed_image_scale_up(Image* dst, const Image* src, const Palette* pal, + int x, int y, int opacity, int blend_mode, Zoom zoom) { BlenderHelper blender(src, pal, blend_mode); int src_x, src_y, src_w, src_h; @@ -126,8 +125,8 @@ static void merge_zoomed_image(Image* dst, const Image* src, const Palette* pal, int first_box_w, first_box_h; int line_h, bottom; - box_w = 1<width()<height()<width()); + dst_h = zoom.apply(src->height()); // clipping... if (dst_x < 0) { - src_x += (-dst_x)>>zoom; - src_w -= (-dst_x)>>zoom; + src_x += zoom.remove(-dst_x); + src_w -= zoom.remove(-dst_x); dst_w -= (-dst_x); first_box_w = box_w - ((-dst_x) % box_w); dst_x = 0; @@ -151,8 +150,8 @@ static void merge_zoomed_image(Image* dst, const Image* src, const Palette* pal, first_box_w = 0; if (dst_y < 0) { - src_y += (-dst_y)>>zoom; - src_h -= (-dst_y)>>zoom; + src_y += zoom.remove(-dst_y); + src_h -= zoom.remove(-dst_y); dst_h -= (-dst_y); first_box_h = box_h - ((-dst_y) % box_h); dst_y = 0; @@ -161,12 +160,12 @@ static void merge_zoomed_image(Image* dst, const Image* src, const Palette* pal, first_box_h = 0; if (dst_x+dst_w > dst->width()) { - src_w -= (dst_x+dst_w-dst->width()) >> zoom; + src_w -= zoom.remove(dst_x+dst_w-dst->width()); dst_w = dst->width() - dst_x; } if (dst_y+dst_h > dst->height()) { - src_h -= (dst_y+dst_h-dst->height()) >> zoom; + src_h -= zoom.remove(dst_y+dst_h-dst->height()); dst_h = dst->height() - dst_y; } @@ -279,6 +278,107 @@ done_with_line:; done_with_blit:; } +template +static void merge_zoomed_image_scale_down(Image* dst, const Image* src, const Palette* pal, + int x, int y, int opacity, int blend_mode, Zoom zoom) +{ + BlenderHelper blender(src, pal, blend_mode); + int src_x, src_y, src_w, src_h; + int dst_x, dst_y, dst_w, dst_h; + int unbox_w, unbox_h; + int bottom; + + unbox_w = zoom.remove(1); + unbox_h = zoom.remove(1); + + src_x = 0; + src_y = 0; + src_w = src->width(); + src_h = src->height(); + + dst_x = x; + dst_y = y; + dst_w = zoom.apply(src->width()); + dst_h = zoom.apply(src->height()); + + // clipping... + if (dst_x < 0) { + src_x += zoom.remove(-dst_x); + src_w -= zoom.remove(-dst_x); + dst_w -= (-dst_x); + dst_x = 0; + } + + if (dst_y < 0) { + src_y += zoom.remove(-dst_y); + src_h -= zoom.remove(-dst_y); + dst_h -= (-dst_y); + dst_y = 0; + } + + if (dst_x+dst_w > dst->width()) { + src_w -= zoom.remove(dst_x+dst_w-dst->width()); + dst_w = dst->width() - dst_x; + } + + if (dst_y+dst_h > dst->height()) { + src_h -= zoom.remove(dst_y+dst_h-dst->height()); + dst_h = dst->height() - dst_y; + } + + src_w = zoom.remove(zoom.apply(src_w)); + src_h = zoom.remove(zoom.apply(src_h)); + + if ((src_w <= 0) || (src_h <= 0) || + (dst_w <= 0) || (dst_h <= 0)) + return; + + bottom = dst_y+dst_h-1; + + // Lock all necessary bits + const LockImageBits srcBits(src, gfx::Rect(src_x, src_y, src_w, src_h)); + LockImageBits dstBits(dst, gfx::Rect(dst_x, dst_y, dst_w, dst_h)); + typename LockImageBits::const_iterator src_it = srcBits.begin(); + typename LockImageBits::const_iterator src_end = srcBits.end(); + typename LockImageBits::iterator dst_it, dst_end; + + // For each line to draw of the source image... + for (y=0; y= srcBits.begin() && src_it < src_end); + ASSERT(dst_it >= dstBits.begin() && dst_it < dst_end); + + blender(*dst_it, *dst_it, *src_it, opacity); + + // Skip source pixels + for (int delta=0; delta < unbox_w && src_it != src_end; ++delta) + ++src_it; + + ++dst_it; + } + + if (++dst_y > bottom) + break; + + // Skip lines + for (int delta=0; delta < src_w * (unbox_h-1) && src_it != src_end; ++delta) + ++src_it; + } +} + +template +static void merge_zoomed_image(Image* dst, const Image* src, const Palette* pal, + int x, int y, int opacity, int blend_mode, Zoom zoom) +{ + if (zoom.scale() >= 1.0) + merge_zoomed_image_scale_up(dst, src, pal, x, y, opacity, blend_mode, zoom); + else + merge_zoomed_image_scale_down(dst, src, pal, x, y, opacity, blend_mode, zoom); +} + ////////////////////////////////////////////////////////////////////// // Render Engine @@ -380,15 +480,15 @@ void RenderEngine::setPreviewImage(const Layer* layer, FrameNumber frame, Image* in a new image and return it. Positions source_x, source_y, width and height must have the - zoom applied (sorce_x<backgroundLayer(); bool need_checked_bg = (background != NULL ? !background->isVisible(): true); uint32_t bg_color = 0; @@ -471,7 +571,7 @@ Image* RenderEngine::renderSprite(int source_x, int source_y, // static void RenderEngine::renderCheckedBackground(Image* image, int source_x, int source_y, - int zoom) + Zoom zoom) { int x, y, u, v; int tile_w = 16; @@ -504,13 +604,13 @@ void RenderEngine::renderCheckedBackground(Image* image, } if (checked_bg_zoom) { - tile_w <<= zoom; - tile_h <<= zoom; + tile_w = zoom.apply(tile_w); + tile_h = zoom.apply(tile_h); } // Tile size - if (tile_w < (1<pixelFormat() == IMAGE_RGB && "renderImage accepts RGB destination images only"); @@ -566,8 +666,8 @@ void RenderEngine::renderLayer( const Layer* layer, Image *image, int source_x, int source_y, - FrameNumber frame, int zoom, - void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, int), + FrameNumber frame, Zoom zoom, + void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, Zoom), bool render_background, bool render_transparent, int blend_mode) @@ -607,8 +707,8 @@ void RenderEngine::renderLayer( ASSERT(src_image->maskColor() == m_sprite->transparentColor()); (*zoomed_func)(image, src_image, m_sprite->getPalette(frame), - (cel->x() << zoom) - source_x, - (cel->y() << zoom) - source_y, + zoom.apply(cel->x()) - source_x, + zoom.apply(cel->y()) - source_y, output_opacity, (blend_mode < 0 ? static_cast(layer)->getBlendMode(): @@ -645,8 +745,8 @@ void RenderEngine::renderLayer( Image* extraImage = m_document->getExtraCelImage(); (*zoomed_func)(image, extraImage, m_sprite->getPalette(frame), - (extraCel->x() << zoom) - source_x, - (extraCel->y() << zoom) - source_y, + zoom.apply(extraCel->x()) - source_x, + zoom.apply(extraCel->y()) - source_y, extraCel->opacity(), m_document->getExtraCelBlendMode(), zoom); } diff --git a/src/app/util/render.h b/src/app/util/render.h index 4913f073dc..6e0559d72d 100644 --- a/src/app/util/render.h +++ b/src/app/util/render.h @@ -21,6 +21,7 @@ #pragma once #include "app/color.h" +#include "app/zoom.h" #include "doc/frame_number.h" namespace doc { @@ -70,7 +71,7 @@ namespace app { Image* renderSprite(int source_x, int source_y, int width, int height, - FrameNumber frame, int zoom, + FrameNumber frame, Zoom zoom, bool draw_tiled_bg, bool enable_onionskin); @@ -79,18 +80,18 @@ namespace app { static void renderCheckedBackground(Image* image, int source_x, int source_y, - int zoom); + Zoom zoom); static void renderImage(Image* rgb_image, Image* src_image, const Palette* pal, - int x, int y, int zoom); + int x, int y, Zoom zoom); private: void renderLayer( const Layer* layer, Image* image, int source_x, int source_y, - FrameNumber frame, int zoom, - void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, int), + FrameNumber frame, Zoom zoom, + void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, Zoom), bool render_background, bool render_transparent, int blend_mode); diff --git a/src/app/zoom.cpp b/src/app/zoom.cpp new file mode 100644 index 0000000000..d259d087ef --- /dev/null +++ b/src/app/zoom.cpp @@ -0,0 +1,47 @@ +/* Aseprite + * Copyright (C) 2001-2014 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/zoom.h" + +namespace app { + +void Zoom::in() +{ + if (m_den > 1) { + m_den--; + } + else if (m_num < 64) { + m_num++; + } +} + +void Zoom::out() +{ + if (m_num > 1) { + m_num--; + } + else if (m_den < 32) { + m_den++; + } +} + +} // namespace app diff --git a/src/app/zoom.h b/src/app/zoom.h new file mode 100644 index 0000000000..873efaee4a --- /dev/null +++ b/src/app/zoom.h @@ -0,0 +1,57 @@ +/* Aseprite + * Copyright (C) 2001-2014 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef APP_ZOOM_H_INCLUDED +#define APP_ZOOM_H_INCLUDED +#pragma once + +namespace app { + + class Zoom { + public: + Zoom(int num, int den) + : m_num(num), m_den(den) { + } + + double scale() const { return static_cast(m_num) / static_cast(m_den); } + + int apply(int x) const { return x * m_num / m_den; } + int remove(int x) const { return x * m_den / m_num; } + + double apply(double x) const { return x * m_num / m_den; } + double remove(double x) const { return x * m_den / m_num; } + + void in(); + void out(); + + bool operator==(const Zoom& other) const { + return m_num == other.m_num && m_den == other.m_den; + } + + bool operator!=(const Zoom& other) const { + return !operator==(other); + } + + private: + int m_num; + int m_den; + }; + +} // namespace app + +#endif // APP_ZOOM_H_INCLUDED