diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h index c2953b825..661d6d105 100644 --- a/src/app/tools/ink_processing.h +++ b/src/app/tools/ink_processing.h @@ -1102,6 +1102,7 @@ public: m_height = m_brush->bounds().h; m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width; m_v = (m_brush->patternOrigin().y - loop->getCelOrigin().y) % m_height; + m_transparentColor = loop->sprite()->transparentColor(); } void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override { @@ -1132,6 +1133,11 @@ private: const Image* m_brushMask; int m_opacity; int m_u, m_v, m_width, m_height; + + // When we have a image brush from an INDEXED sprite, we need to know + // which is the background color in order to translate to transparent color + // in a RGBA sprite. + color_t m_transparentColor; }; template<> @@ -1144,20 +1150,36 @@ void BrushInkProcessing::processPixel(int x, int y) { switch (m_brushImage->pixelFormat()) { case IMAGE_RGB: { c = get_pixel_fast(m_brushImage, x, y); + + // We blend the previous image brush pixel with a pixel from the + // image preview (*m_dstAddress). Yes, dstImage, in that way we + // can overlap image brush self printed areas (auto compose + // colors). Doing this, we avoid eraser action of the pixels + // with alpha <255 in the image brush. + c = rgba_blender_normal(*m_dstAddress, c, m_opacity); break; } case IMAGE_INDEXED: { + // TODO m_palette->getEntry(c) does not work because the m_palette member is + // loaded the Graya Palette, NOT the original Indexed Palette from where m_brushImage belongs. + // This conversion can be possible if we load the palette pointer in m_brush when + // is created the custom brush in the Indexed Sprite. c = get_pixel_fast(m_brushImage, x, y); - c = m_palette->getEntry(c); + if (m_transparentColor == c) + c = 0; + else + c = m_palette->getEntry(c); + c = rgba_blender_normal(*m_dstAddress, c, m_opacity); break; } case IMAGE_GRAYSCALE: { c = get_pixel_fast(m_brushImage, x, y); - // TODO review this line - c = graya(m_palette->getEntry(c), graya_geta(c)); + c = rgba(graya_getv(c), graya_getv(c), graya_getv(c), graya_geta(c)); + c = rgba_blender_normal(*m_dstAddress, c, m_opacity); break; } case IMAGE_BITMAP: { + // TODO In which circuntance is possible this case? c = get_pixel_fast(m_brushImage, x, y); c = c ? m_fgColor: m_bgColor; break; @@ -1166,8 +1188,7 @@ void BrushInkProcessing::processPixel(int x, int y) { ASSERT(false); return; } - - *m_dstAddress = rgba_blender_normal(*m_srcAddress, c, m_opacity); + *m_dstAddress = c; } template<> @@ -1180,22 +1201,33 @@ void BrushInkProcessing::processPixel(int x, int y) { switch (m_brushImage->pixelFormat()) { case IMAGE_RGB: { c = get_pixel_fast(m_brushImage, x, y); - c = graya(int(rgba_getr(c)) + int(rgba_getg(c)) + int(rgba_getb(c)) / 3, - rgba_geta(c)); + c = graya(rgba_luma(c), rgba_geta(c)); + c = graya_blender_normal(*m_dstAddress, c, m_opacity); break; } case IMAGE_INDEXED: { + // TODO m_palette->getEntry(c) does not work because the + // m_palette member is loaded the Graya Palette, NOT the + // original Indexed Palette from where m_brushImage belongs. + // This conversion can be possible if we load the palette + // pointer in m_brush when is created the custom brush in the + // Indexed Sprite. c = get_pixel_fast(m_brushImage, x, y); - c = m_palette->getEntry(c); - c = graya(int(rgba_getr(c)) + int(rgba_getg(c)) + int(rgba_getb(c)) / 3, - rgba_geta(c)); + if (m_transparentColor == c) + c = 0; + else + c = m_palette->getEntry(c); + c = graya(rgba_luma(c), rgba_geta(c)); + c = graya_blender_normal(*m_dstAddress, c, m_opacity); break; } case IMAGE_GRAYSCALE: { c = get_pixel_fast(m_brushImage, x, y); + c = graya_blender_normal(*m_dstAddress, c, m_opacity); break; } case IMAGE_BITMAP: { + // TODO In which circuntance is possible this case? c = get_pixel_fast(m_brushImage, x, y); c = c ? m_fgColor: m_bgColor; break; @@ -1204,8 +1236,7 @@ void BrushInkProcessing::processPixel(int x, int y) { ASSERT(false); return; } - - *m_dstAddress = graya_blender_normal(*m_srcAddress, c, m_opacity); + *m_dstAddress = c; } template<> @@ -1234,6 +1265,7 @@ void BrushInkProcessing::processPixel(int x, int y) { break; } case IMAGE_BITMAP: { + // TODO In which circuntance is possible this case? c = get_pixel_fast(m_brushImage, x, y); c = c ? m_fgColor: m_bgColor; break; @@ -1242,8 +1274,8 @@ void BrushInkProcessing::processPixel(int x, int y) { ASSERT(false); return; } - - *m_dstAddress = c; + if (c != m_transparentColor) + *m_dstAddress = c; } ////////////////////////////////////////////////////////////////////// diff --git a/src/app/tools/intertwiners.h b/src/app/tools/intertwiners.h index 2d34f3c11..8e141b60c 100644 --- a/src/app/tools/intertwiners.h +++ b/src/app/tools/intertwiners.h @@ -59,6 +59,15 @@ public: }; class IntertwineAsLines : public Intertwine { + static void Line(int x, int y, Stroke* stroke) { + gfx::Point newPoint(x, y); + if (stroke->empty() || + stroke->lastPoint() != newPoint) { + stroke->addPoint(newPoint); + } + } + + Stroke m_pts; public: bool snapByAngle() override { return true; } @@ -71,14 +80,23 @@ public: doPointshapePoint(stroke[0].x, stroke[0].y, loop); } else if (stroke.size() >= 2) { - for (int c=0; c+1invalidateDstImage(m_dirtyArea); + m_toolLoop->invalidateDstImage(); + m_toolLoop->validateDstImage(gfx::Region(m_toolLoop->getDstImage()->bounds())); } m_toolLoop->validateDstImage(m_dirtyArea); @@ -214,8 +215,18 @@ void ToolLoopManager::doLoopStep(bool lastStep) // Join or fill user points if (!m_toolLoop->getFilled() || (!lastStep && !m_toolLoop->getPreviewFilled())) m_toolLoop->getIntertwine()->joinStroke(m_toolLoop, main_stroke); - else + else { + // Filled + Freehand Controller = Contour Tool + if (m_toolLoop->getController()->isFreehand()) { + // With this we avoid over-drawing the edge of the contour, + // appreciated when we use the Contour Tool combined with Image + // Brush with alpha content. + m_toolLoop->invalidateDstImage(); + m_toolLoop->validateDstImage(gfx::Region(m_toolLoop->getDstImage()->bounds())); + } + m_toolLoop->getIntertwine()->fillStroke(m_toolLoop, main_stroke); + } if (m_toolLoop->getTracePolicy() == TracePolicy::Overlap) { // Copy destination to source (yes, destination to source). In