From 18b4008c6f76cc57fc166be01e201ebc0d0d7377 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 19 Sep 2016 15:14:41 -0300 Subject: [PATCH 1/3] Fix issues creating brushes with transparent color --- src/app/app_brushes.cpp | 36 +++++++++++++++----- src/app/commands/cmd_new_brush.cpp | 2 +- src/app/tools/ink_processing.h | 8 +++++ src/app/tools/point_shapes.h | 4 ++- src/app/ui/editor/brush_preview.cpp | 24 ++++++------- src/doc/brush.cpp | 43 ++++++++++++++++++++--- src/doc/brush.h | 5 ++- src/doc/compressed_image.cpp | 53 ++++++++++++++++++----------- src/doc/compressed_image.h | 5 +-- 9 files changed, 130 insertions(+), 50 deletions(-) diff --git a/src/app/app_brushes.cpp b/src/app/app_brushes.cpp index aa2cb59d4..c099fb5bc 100644 --- a/src/app/app_brushes.cpp +++ b/src/app/app_brushes.cpp @@ -95,6 +95,17 @@ ImageRef load_xml_image(const TiXmlElement* imageElem) ++it; } } + else if (formatStr == "bitmap") { + image.reset(Image::create(IMAGE_BITMAP, w, h)); + LockImageBits pixels(image.get()); + for (auto& pixel : pixels) { + if (it == end) + break; + + pixel = *it; + ++it; + } + } return image; } @@ -110,7 +121,7 @@ void save_xml_image(TiXmlElement* imageElem, const Image* image) case IMAGE_RGB: format = "rgba"; break; case IMAGE_GRAYSCALE: format = "grayscale"; break; case IMAGE_INDEXED: format = "indexed"; break; - case IMAGE_BITMAP: format = "indexed"; break; // TODO add "bitmap" format + case IMAGE_BITMAP: format = "bitmap"; break; // TODO add "bitmap" format } ASSERT(!format.empty()); if (!format.empty()) @@ -315,13 +326,16 @@ void AppBrushes::load(const std::string& filename) } // Brush image - if (TiXmlElement* imageElem = brushElem->FirstChildElement("image")) { - ImageRef image = load_xml_image(imageElem); - if (image) { - if (!brush) - brush.reset(new Brush()); - brush->setImage(image.get()); - } + ImageRef image, mask; + if (TiXmlElement* imageElem = brushElem->FirstChildElement("image")) + image = load_xml_image(imageElem); + if (TiXmlElement* maskElem = brushElem->FirstChildElement("mask")) + mask = load_xml_image(maskElem); + + if (image) { + if (!brush) + brush.reset(new Brush()); + brush->setImage(image.get(), mask.get()); } // Colors @@ -421,6 +435,12 @@ void AppBrushes::save(const std::string& filename) const TiXmlElement elem("image"); save_xml_image(&elem, slot.brush()->image()); brushElem.InsertEndChild(elem); + + if (slot.brush()->maskBitmap()) { + TiXmlElement maskElem("mask"); + save_xml_image(&maskElem, slot.brush()->maskBitmap()); + brushElem.InsertEndChild(maskElem); + } } } diff --git a/src/app/commands/cmd_new_brush.cpp b/src/app/commands/cmd_new_brush.cpp index d8cd5cc2c..4f173d74d 100644 --- a/src/app/commands/cmd_new_brush.cpp +++ b/src/app/commands/cmd_new_brush.cpp @@ -146,7 +146,7 @@ void NewBrushCommand::createBrush(const Site& site, const Mask* mask) // New brush doc::BrushRef brush(new doc::Brush()); - brush->setImage(image.get()); + brush->setImage(image.get(), mask->bitmap()); brush->setPatternOrigin(mask->bounds().origin()); ContextBar* ctxBar = App::instance()->contextBar(); diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h index 5b322f1f6..a0572cbbc 100644 --- a/src/app/tools/ink_processing.h +++ b/src/app/tools/ink_processing.h @@ -922,6 +922,7 @@ public: m_palette = get_current_palette(); m_brush = loop->getBrush(); m_brushImage = m_brush->image(); + m_brushMask = m_brush->maskBitmap(); m_opacity = loop->getOpacity(); m_width = m_brush->bounds().w; m_height = m_brush->bounds().h; @@ -946,6 +947,7 @@ private: const Palette* m_palette; const Brush* m_brush; const Image* m_brushImage; + const Image* m_brushMask; int m_opacity; int m_u, m_v, m_width, m_height; }; @@ -953,6 +955,8 @@ private: template<> void BrushInkProcessing::processPixel(int x, int y) { alignPixelPoint(x, y); + if (m_brushMask && !get_pixel_fast(m_brushMask, x, y)) + return; color_t c; switch (m_brushImage->pixelFormat()) { @@ -987,6 +991,8 @@ void BrushInkProcessing::processPixel(int x, int y) { template<> void BrushInkProcessing::processPixel(int x, int y) { alignPixelPoint(x, y); + if (m_brushMask && !get_pixel_fast(m_brushMask, x, y)) + return; color_t c; switch (m_brushImage->pixelFormat()) { @@ -1023,6 +1029,8 @@ void BrushInkProcessing::processPixel(int x, int y) { template<> void BrushInkProcessing::processPixel(int x, int y) { alignPixelPoint(x, y); + if (m_brushMask && !get_pixel_fast(m_brushMask, x, y)) + return; color_t c; switch (m_brushImage->pixelFormat()) { diff --git a/src/app/tools/point_shapes.h b/src/app/tools/point_shapes.h index 92a20a8f6..14f84f961 100644 --- a/src/app/tools/point_shapes.h +++ b/src/app/tools/point_shapes.h @@ -40,7 +40,9 @@ public: void preparePointShape(ToolLoop* loop) override { m_brush = loop->getBrush(); - m_compressedImage.reset(new CompressedImage(m_brush->image(), false)); + m_compressedImage.reset(new CompressedImage(m_brush->image(), + m_brush->maskBitmap(), + false)); m_firstPoint = true; } diff --git a/src/app/ui/editor/brush_preview.cpp b/src/app/ui/editor/brush_preview.cpp index b6c79aa72..5999ffadd 100644 --- a/src/app/ui/editor/brush_preview.cpp +++ b/src/app/ui/editor/brush_preview.cpp @@ -338,27 +338,23 @@ void BrushPreview::generateBoundaries() m_brushWidth = w; m_brushHeight = h; - ImageRef mask; + Image* mask = nullptr; + bool deleteMask = true; if (isOnePixel) { - mask.reset(Image::create(IMAGE_BITMAP, w, w)); + mask = Image::create(IMAGE_BITMAP, 1, 1); mask->putPixel(0, 0, (color_t)1); } else if (brushImage->pixelFormat() != IMAGE_BITMAP) { - mask.reset(Image::create(IMAGE_BITMAP, w, h)); - - LockImageBits bits(mask.get()); - auto pos = bits.begin(); - for (int v=0; vmaskBitmap()); + deleteMask = false; + mask = brush->maskBitmap(); } m_brushBoundaries.reset( - new MaskBoundaries( - (mask ? mask.get(): brushImage))); + new MaskBoundaries(mask ? mask: brushImage)); + + if (deleteMask) + delete mask; } void BrushPreview::forEachBrushPixel( diff --git a/src/doc/brush.cpp b/src/doc/brush.cpp index 840e4a3c4..7000dc4a3 100644 --- a/src/doc/brush.cpp +++ b/src/doc/brush.cpp @@ -51,6 +51,7 @@ Brush::Brush(const Brush& brush) m_size = brush.m_size; m_angle = brush.m_angle; m_image = brush.m_image; + m_maskBitmap = brush.m_maskBitmap; m_pattern = brush.m_pattern; m_patternOrigin = brush.m_patternOrigin; m_gen = 0; @@ -84,10 +85,24 @@ void Brush::setAngle(int angle) regenerate(); } -void Brush::setImage(const Image* image) +void Brush::setImage(const Image* image, + const Image* maskBitmap) { m_type = kImageBrushType; m_image.reset(Image::createCopy(image)); + if (maskBitmap) + m_maskBitmap.reset(Image::createCopy(maskBitmap)); + else { + int w = image->width(); + int h = image->height(); + m_maskBitmap.reset(Image::create(IMAGE_BITMAP, w, h)); + LockImageBits bits(m_maskBitmap.get()); + auto pos = bits.begin(); + for (int v=0; vmaskColor()); + } + m_backupImage.reset(); m_mainColor.reset(); m_bgColor.reset(); @@ -102,15 +117,21 @@ template static void replace_image_colors( Image* image, + Image* maskBitmap, const bool useMain, color_t mainColor, const bool useBg, color_t bgColor) { LockImageBits bits(image, Image::ReadWriteLock); + const LockImageBits maskBits(maskBitmap); bool hasAlpha = false; // True if "image" has a pixel with alpha < 255 color_t srcMainColor, srcBgColor; srcMainColor = srcBgColor = 0; + auto mask_it = maskBits.begin(); for (const auto& pixel : bits) { + if (!*mask_it) + continue; + if ((pixel & alpha_mask) != alpha_mask) { // If alpha != 255 hasAlpha = true; } @@ -120,6 +141,8 @@ static void replace_image_colors( else if (pixel != srcBgColor && srcMainColor == srcBgColor) { srcMainColor = pixel; } + + ++mask_it; } mainColor &= color_mask; @@ -147,16 +170,22 @@ static void replace_image_colors( static void replace_image_colors_indexed( Image* image, + Image* maskBitmap, const bool useMain, const color_t mainColor, const bool useBg, const color_t bgColor) { LockImageBits bits(image, Image::ReadWriteLock); + const LockImageBits maskBits(maskBitmap); bool hasAlpha = false; // True if "image" has a pixel with the mask color color_t maskColor = image->maskColor(); color_t srcMainColor, srcBgColor; srcMainColor = srcBgColor = maskColor; + auto mask_it = maskBits.begin(); for (const auto& pixel : bits) { + if (!*mask_it) + continue; + if (pixel == maskColor) { hasAlpha = true; } @@ -166,6 +195,8 @@ static void replace_image_colors_indexed( else if (pixel != srcBgColor && srcMainColor == srcBgColor) { srcMainColor = pixel; } + + ++mask_it; } if (hasAlpha) { @@ -201,6 +232,8 @@ void Brush::setImageColor(ImageColor imageColor, color_t color) else m_image.reset(Image::createCopy(m_backupImage.get())); + ASSERT(m_maskBitmap); + switch (imageColor) { case ImageColor::MainColor: m_mainColor.reset(new color_t(color)); @@ -214,21 +247,21 @@ void Brush::setImageColor(ImageColor imageColor, color_t color) case IMAGE_RGB: replace_image_colors( - m_image.get(), + m_image.get(), m_maskBitmap.get(), (m_mainColor ? true: false), (m_mainColor ? *m_mainColor: 0), (m_bgColor ? true: false), (m_bgColor ? *m_bgColor: 0)); break; case IMAGE_GRAYSCALE: replace_image_colors( - m_image.get(), + m_image.get(), m_maskBitmap.get(), (m_mainColor ? true: false), (m_mainColor ? *m_mainColor: 0), (m_bgColor ? true: false), (m_bgColor ? *m_bgColor: 0)); break; case IMAGE_INDEXED: replace_image_colors_indexed( - m_image.get(), + m_image.get(), m_maskBitmap.get(), (m_mainColor ? true: false), (m_mainColor ? *m_mainColor: 0), (m_bgColor ? true: false), (m_bgColor ? *m_bgColor: 0)); break; @@ -240,6 +273,7 @@ void Brush::clean() { m_gen = ++generation; m_image.reset(); + m_maskBitmap.reset(); m_backupImage.reset(); } @@ -260,6 +294,7 @@ void Brush::regenerate() size = (int)std::sqrt((double)2*m_size*m_size)+2; m_image.reset(Image::create(IMAGE_BITMAP, size, size)); + m_maskBitmap.reset(); if (size == 1) { clear_image(m_image.get(), BitmapTraits::max_value); diff --git a/src/doc/brush.h b/src/doc/brush.h index 026810744..5f6352728 100644 --- a/src/doc/brush.h +++ b/src/doc/brush.h @@ -36,6 +36,7 @@ namespace doc { int size() const { return m_size; } int angle() const { return m_angle; } Image* image() const { return m_image.get(); } + Image* maskBitmap() const { return m_maskBitmap.get(); } int gen() const { return m_gen; } BrushPattern pattern() const { return m_pattern; } @@ -46,7 +47,8 @@ namespace doc { void setType(BrushType type); void setSize(int size); void setAngle(int angle); - void setImage(const Image* image); + void setImage(const Image* image, + const Image* maskBitmap); void setImageColor(ImageColor imageColor, color_t color); void setPattern(BrushPattern pattern) { m_pattern = pattern; @@ -63,6 +65,7 @@ namespace doc { int m_size; // Size (diameter) int m_angle; // Angle in degrees 0-360 ImageRef m_image; // Image of the brush + ImageRef m_maskBitmap; gfx::Rect m_bounds; BrushPattern m_pattern; // How the image should be replicated gfx::Point m_patternOrigin; // From what position the brush was taken diff --git a/src/doc/compressed_image.cpp b/src/doc/compressed_image.cpp index b94988ee5..b654db345 100644 --- a/src/doc/compressed_image.cpp +++ b/src/doc/compressed_image.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2001-2015 David Capello +// Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -11,10 +11,13 @@ #include "doc/compressed_image.h" #include "doc/primitives.h" +#include "doc/primitives_fast.h" namespace doc { -CompressedImage::CompressedImage(const Image* image, bool diffColors) +CompressedImage::CompressedImage(const Image* image, + const Image* maskBitmap, + bool diffColors) : m_image(image) { color_t c1, c2, mask = image->maskColor(); @@ -23,24 +26,36 @@ CompressedImage::CompressedImage(const Image* image, bool diffColors) Scanline scanline(y); for (int x=0; xwidth(); ) { - c1 = get_pixel(image, x, y); - if (c1 != mask) { - scanline.color = c1; - scanline.x = x; - - for (++x; xwidth(); ++x) { - c2 = get_pixel(image, x, y); - - if ((diffColors && c1 != c2) || - (!diffColors && c2 == mask)) - break; - } - - scanline.w = x - scanline.x; - m_scanlines.push_back(scanline); - } - else + if (maskBitmap && !get_pixel_fast(maskBitmap, x, y)) { ++x; + continue; + } + + c1 = get_pixel(image, x, y); + + if (!maskBitmap && c1 == mask) { + ++x; + continue; + } + + scanline.color = c1; + scanline.x = x; + + for (++x; xwidth(); ++x) { + c2 = get_pixel(image, x, y); + + if (diffColors && c1 != c2) + break; + + if (maskBitmap && !get_pixel_fast(maskBitmap, x, y)) + break; + + if (!diffColors && !maskBitmap && c2 == mask) + break; + } + + scanline.w = x - scanline.x; + m_scanlines.push_back(scanline); } } } diff --git a/src/doc/compressed_image.h b/src/doc/compressed_image.h index b9fff4518..8673e333d 100644 --- a/src/doc/compressed_image.h +++ b/src/doc/compressed_image.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2001-2015 David Capello +// Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -30,7 +30,8 @@ namespace doc { // each different color. If it's false, it generates a scanline // for each row of consecutive pixels different than the mask // color. - CompressedImage(const Image* image, bool diffColors); + CompressedImage(const Image* image, + const Image* maskBitmap, bool diffColors); const_iterator begin() const { return m_scanlines.begin(); } const_iterator end() const { return m_scanlines.end(); } From 03c74ceeb7926cac2334b144476c1ecf61564f65 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 19 Sep 2016 16:49:24 -0300 Subject: [PATCH 2/3] Make "stop at grid" disabled by default (related to #473) --- data/pref.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/pref.xml b/data/pref.xml index 3c9cbb505..66488f459 100644 --- a/data/pref.xml +++ b/data/pref.xml @@ -232,7 +232,7 @@