mirror of https://github.com/aseprite/aseprite.git
				
				
				
			The code was refactored moving the BlenderHelper class from "render" to "doc", and now doc::blend_image() supports blending different color modes. Some work is still needed to work with grayscale images correctly.
This commit is contained in:
		
							parent
							
								
									31f3c79566
								
							
						
					
					
						commit
						8f7bf09263
					
				
							
								
								
									
										2
									
								
								laf
								
								
								
								
							
							
								
								
								
								
								
								
							
						
						
									
										2
									
								
								laf
								
								
								
								
							|  | @ -1 +1 @@ | |||
| Subproject commit c23da3b0516540036ee501e6a35de9afc0356ab4 | ||||
| Subproject commit 26994fe6c1210e0989eaddd4b2bdc00422e1ac8a | ||||
|  | @ -1,5 +1,5 @@ | |||
| // Aseprite
 | ||||
| // Copyright (C) 2022  Igara Studio S.A.
 | ||||
| // Copyright (C) 2022-2024  Igara Studio S.A.
 | ||||
| // Copyright (C) 2001-2018  David Capello
 | ||||
| //
 | ||||
| // This program is distributed under the terms of
 | ||||
|  | @ -12,9 +12,11 @@ | |||
| #include "app/modules/palettes.h" | ||||
| 
 | ||||
| #include "app/app.h" | ||||
| #include "app/context.h" | ||||
| #include "app/extensions.h" | ||||
| #include "app/file/palette_file.h" | ||||
| #include "app/resource_finder.h" | ||||
| #include "app/site.h" | ||||
| #include "base/fs.h" | ||||
| #include "doc/image.h" | ||||
| #include "doc/palette.h" | ||||
|  | @ -125,6 +127,15 @@ void load_default_palette() | |||
| //      function and use the active Site palette.
 | ||||
| Palette* get_current_palette() | ||||
| { | ||||
| #if !ENABLE_UI | ||||
|   if (auto* app = App::instance()) { | ||||
|     if (auto* ctx = app->context()) { | ||||
|       Site site = ctx->activeSite(); | ||||
|       if (site.sprite()) | ||||
|         return site.palette(); | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
|   return ase_current_palette; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| // Aseprite
 | ||||
| // Copyright (C) 2018-2023  Igara Studio S.A.
 | ||||
| // Copyright (C) 2018-2024  Igara Studio S.A.
 | ||||
| // Copyright (C) 2015-2018  David Capello
 | ||||
| //
 | ||||
| // This program is distributed under the terms of
 | ||||
|  | @ -16,6 +16,7 @@ | |||
| #include "app/context.h" | ||||
| #include "app/doc.h" | ||||
| #include "app/file/file.h" | ||||
| #include "app/modules/palettes.h" | ||||
| #include "app/script/blend_mode.h" | ||||
| #include "app/script/docobj.h" | ||||
| #include "app/script/engine.h" | ||||
|  | @ -29,6 +30,7 @@ | |||
| #include "doc/algorithm/flip_image.h" | ||||
| #include "doc/algorithm/flip_type.h" | ||||
| #include "doc/algorithm/shrink_bounds.h" | ||||
| #include "doc/blend_image.h" | ||||
| #include "doc/cel.h" | ||||
| #include "doc/image.h" | ||||
| #include "doc/image_ref.h" | ||||
|  | @ -312,13 +314,18 @@ int Image_drawImage(lua_State* L) | |||
|   const Image* src = sprite->image(L); | ||||
| 
 | ||||
|   if (auto cel = obj->cel(L)) { | ||||
|     gfx::Rect bounds(0, 0, src->size().w, src->size().h); | ||||
|     buf.reset(new doc::ImageBuffer); | ||||
|     ImageRef tmp_src( | ||||
|       doc::crop_image(dst, | ||||
|                       gfx::Rect(pos.x, pos.y, src->size().w, src->size().h), | ||||
|                       0, buf)); | ||||
|     doc::blend_image(tmp_src.get(), src, 0, 0, opacity, blendMode); | ||||
|     gfx::Rect bounds(src->size()); | ||||
| 
 | ||||
|     // Create the ImageBuffer only when it doesn't exist so we can
 | ||||
|     // cache the allocated buffer.
 | ||||
|     if (!buf) | ||||
|       buf = std::make_shared<doc::ImageBuffer>(); | ||||
| 
 | ||||
|     ImageRef tmp_src(doc::crop_image(dst, gfx::Rect(pos, src->size()), 0, buf)); | ||||
|     doc::blend_image(tmp_src.get(), src, | ||||
|                      gfx::Clip(src->size()), | ||||
|                      cel->sprite()->palette(0), | ||||
|                      opacity, blendMode); | ||||
|     // TODO Use something similar to doc::algorithm::shrink_bounds2()
 | ||||
|     //      but we need something that does the render and compares
 | ||||
|     //      the minimal modified area.
 | ||||
|  | @ -332,7 +339,8 @@ int Image_drawImage(lua_State* L) | |||
|   // the source image without undo information.
 | ||||
|   else { | ||||
|     doc::blend_image(dst, src, | ||||
|                      pos.x, pos.y, | ||||
|                      gfx::Clip(pos, src->bounds()), | ||||
|                      get_current_palette(), | ||||
|                      opacity, blendMode); | ||||
|   } | ||||
|   return 0; | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| # Aseprite Document Library | ||||
| # Copyright (C) 2019-2023 Igara Studio S.A. | ||||
| # Copyright (C) 2019-2024 Igara Studio S.A. | ||||
| # Copyright (C) 2001-2018 David Capello | ||||
| 
 | ||||
| if(WIN32) | ||||
|  | @ -22,6 +22,7 @@ add_library(doc-lib | |||
|   algorithm/stroke_selection.cpp | ||||
|   anidir.cpp | ||||
|   blend_funcs.cpp | ||||
|   blend_image.cpp | ||||
|   blend_mode.cpp | ||||
|   brush.cpp | ||||
|   brush_type.cpp | ||||
|  |  | |||
|  | @ -0,0 +1,84 @@ | |||
| // Aseprite Document Library
 | ||||
| // Copyright (c) 2024  Igara Studio S.A.
 | ||||
| //
 | ||||
| // This file is released under the terms of the MIT license.
 | ||||
| // Read LICENSE.txt for more information.
 | ||||
| 
 | ||||
| #ifdef HAVE_CONFIG_H | ||||
| #include "config.h" | ||||
| #endif | ||||
| 
 | ||||
| #include "doc/blend_image.h" | ||||
| 
 | ||||
| #include "doc/blend_internals.h" | ||||
| #include "doc/image_impl.h" | ||||
| 
 | ||||
| namespace doc { | ||||
| 
 | ||||
| template<typename DstTraits, | ||||
|          typename SrcTraits> | ||||
| void blend_image_templ(Image* dst, | ||||
|                        const Image* src, | ||||
|                        const gfx::Clip& area, | ||||
|                        const Palette* pal, | ||||
|                        const int opacity, | ||||
|                        const BlendMode blendMode) | ||||
| { | ||||
|   if constexpr (DstTraits::color_mode == ColorMode::INDEXED || | ||||
|                 SrcTraits::color_mode == ColorMode::INDEXED) { | ||||
|     ASSERT(pal != nullptr); | ||||
|     if (pal == nullptr) | ||||
|       return; | ||||
|   } | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(dst, src, pal, blendMode, true); | ||||
|   LockImageBits<DstTraits> dstBits(dst); | ||||
|   const LockImageBits<SrcTraits> srcBits(src); | ||||
|   auto dstIt = dstBits.begin_area(area.dstBounds()); | ||||
|   auto srcIt = srcBits.begin_area(area.srcBounds()); | ||||
|   auto dstEnd = dstBits.end_area(area.dstBounds()); | ||||
|   for (; dstIt < dstEnd; ++dstIt, ++srcIt) | ||||
|     *dstIt = blender(*dstIt, *srcIt, opacity); | ||||
| } | ||||
| 
 | ||||
| void blend_image(Image* dst, | ||||
|                  const Image* src, | ||||
|                  gfx::Clip area, | ||||
|                  const Palette* pal, | ||||
|                  const int opacity, | ||||
|                  const BlendMode blendMode) | ||||
| { | ||||
|   if (!area.clip(dst->width(), dst->height(), src->width(), src->height())) | ||||
|     return; | ||||
| 
 | ||||
|   switch (dst->pixelFormat()) { | ||||
| 
 | ||||
|     case IMAGE_RGB: | ||||
|       switch (src->pixelFormat()) { | ||||
|         case IMAGE_RGB:       return blend_image_templ<RgbTraits, RgbTraits      >(dst, src, area, pal, opacity, blendMode); | ||||
|         case IMAGE_GRAYSCALE: return blend_image_templ<RgbTraits, GrayscaleTraits>(dst, src, area, pal, opacity, blendMode); | ||||
|         case IMAGE_INDEXED:   return blend_image_templ<RgbTraits, IndexedTraits  >(dst, src, area, pal, opacity, blendMode); | ||||
|       } | ||||
|       break; | ||||
| 
 | ||||
|     case IMAGE_GRAYSCALE: | ||||
|       switch (src->pixelFormat()) { | ||||
|         case IMAGE_RGB:       return blend_image_templ<GrayscaleTraits, RgbTraits      >(dst, src, area, pal, opacity, blendMode); | ||||
|         case IMAGE_GRAYSCALE: return blend_image_templ<GrayscaleTraits, GrayscaleTraits>(dst, src, area, pal, opacity, blendMode); | ||||
|         case IMAGE_INDEXED:   return blend_image_templ<GrayscaleTraits, IndexedTraits  >(dst, src, area, pal, opacity, blendMode); | ||||
|       } | ||||
|       break; | ||||
| 
 | ||||
|     case IMAGE_INDEXED: | ||||
|       switch (src->pixelFormat()) { | ||||
|         case IMAGE_RGB:       return blend_image_templ<IndexedTraits, RgbTraits      >(dst, src, area, pal, opacity, blendMode); | ||||
|         case IMAGE_GRAYSCALE: return blend_image_templ<IndexedTraits, GrayscaleTraits>(dst, src, area, pal, opacity, blendMode); | ||||
|         case IMAGE_INDEXED:   return blend_image_templ<IndexedTraits, IndexedTraits  >(dst, src, area, pal, opacity, blendMode); | ||||
|       } | ||||
|       break; | ||||
| 
 | ||||
|     case IMAGE_TILEMAP: | ||||
|       return dst->copy(src, area); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| } // namespace doc
 | ||||
|  | @ -0,0 +1,30 @@ | |||
| // Aseprite Document Library
 | ||||
| // Copyright (C) 2019-2024  Igara Studio S.A.
 | ||||
| // Copyright (C) 2001-2018  David Capello
 | ||||
| //
 | ||||
| // This file is released under the terms of the MIT license.
 | ||||
| // Read LICENSE.txt for more information.
 | ||||
| 
 | ||||
| #ifndef DOC_BLEND_IMAGE_H_INCLUDED | ||||
| #define DOC_BLEND_IMAGE_H_INCLUDED | ||||
| #pragma once | ||||
| 
 | ||||
| #include "doc/blend_mode.h" | ||||
| #include "gfx/fwd.h" | ||||
| 
 | ||||
| namespace doc { | ||||
|   class Image; | ||||
|   class Palette; | ||||
| 
 | ||||
|   void blend_image(Image* dst, | ||||
|                    const Image* src, | ||||
|                    gfx::Clip area, | ||||
|                    // For indexed color mode
 | ||||
|                    const Palette* pal, | ||||
|                    // For grayscale/RGB color modes
 | ||||
|                    const int opacity, | ||||
|                    const doc::BlendMode blendMode); | ||||
| 
 | ||||
| } // namespace doc
 | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,4 +1,5 @@ | |||
| // Aseprite Document Library
 | ||||
| // Copyright (c) 2024 Igara Studio S.A.
 | ||||
| // Copyright (c) 2001-2015 David Capello
 | ||||
| //
 | ||||
| // This file is released under the terms of the MIT license.
 | ||||
|  | @ -14,4 +15,201 @@ | |||
|   #error Invalid Pixman library | ||||
| #endif | ||||
| 
 | ||||
| #include "doc/blend_funcs.h" | ||||
| #include "doc/blend_mode.h" | ||||
| #include "doc/color.h" | ||||
| #include "doc/image.h" | ||||
| #include "doc/image_traits.h" | ||||
| #include "doc/palette.h" | ||||
| 
 | ||||
| namespace doc { | ||||
| 
 | ||||
|   template<class DstTraits, class SrcTraits> | ||||
|   class BlenderHelper { | ||||
|     BlendFunc m_blendFunc; | ||||
|     color_t m_maskColor; | ||||
|   public: | ||||
|     BlenderHelper(Image* dst, const Image* src, const Palette* pal, | ||||
|                   const BlendMode blendMode, const bool newBlend) | ||||
|     { | ||||
|       m_blendFunc = SrcTraits::get_blender(blendMode, newBlend); | ||||
|       m_maskColor = src->maskColor(); | ||||
|     } | ||||
| 
 | ||||
|     inline typename DstTraits::pixel_t | ||||
|     operator()(typename DstTraits::pixel_t dst, | ||||
|                typename SrcTraits::pixel_t src, | ||||
|                int opacity) | ||||
|     { | ||||
|       if (src != m_maskColor) | ||||
|         return (*m_blendFunc)(dst, src, opacity); | ||||
|       else | ||||
|         return dst; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   //////////////////////////////////////////////////////////////////////
 | ||||
|   // X -> Rgb
 | ||||
| 
 | ||||
|   template<> | ||||
|   class BlenderHelper<RgbTraits, GrayscaleTraits> { | ||||
|     BlendFunc m_blendFunc; | ||||
|     color_t m_maskColor; | ||||
|   public: | ||||
|     BlenderHelper(Image* dst, const Image* src, const Palette* pal, | ||||
|                   const BlendMode blendMode, const bool newBlend) | ||||
|     { | ||||
|       m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); | ||||
|       m_maskColor = src->maskColor(); | ||||
|     } | ||||
| 
 | ||||
|     inline RgbTraits::pixel_t | ||||
|     operator()(RgbTraits::pixel_t dst, | ||||
|                GrayscaleTraits::pixel_t src, | ||||
|                int opacity) | ||||
|     { | ||||
|       if (src != m_maskColor) { | ||||
|         int v = graya_getv(src); | ||||
|         return (*m_blendFunc)(dst, rgba(v, v, v, graya_geta(src)), opacity); | ||||
|       } | ||||
|       else | ||||
|         return dst; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   template<> | ||||
|   class BlenderHelper<RgbTraits, IndexedTraits> { | ||||
|     const Palette* m_pal; | ||||
|     BlendMode m_blendMode; | ||||
|     BlendFunc m_blendFunc; | ||||
|     color_t m_maskColor; | ||||
|   public: | ||||
|     BlenderHelper(Image* dst, const Image* src, const Palette* pal, | ||||
|                   const BlendMode blendMode, const bool newBlend) | ||||
|     { | ||||
|       m_blendMode = blendMode; | ||||
|       m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); | ||||
|       m_maskColor = src->maskColor(); | ||||
|       m_pal = pal; | ||||
|     } | ||||
| 
 | ||||
|     inline RgbTraits::pixel_t | ||||
|     operator()(RgbTraits::pixel_t dst, | ||||
|                IndexedTraits::pixel_t src, | ||||
|                int opacity) | ||||
|     { | ||||
|       if (m_blendMode == BlendMode::SRC) { | ||||
|         return m_pal->getEntry(src); | ||||
|       } | ||||
|       else { | ||||
|         if (src != m_maskColor) { | ||||
|           return (*m_blendFunc)(dst, m_pal->getEntry(src), opacity); | ||||
|         } | ||||
|         else | ||||
|           return dst; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   //////////////////////////////////////////////////////////////////////
 | ||||
|   // X -> Grayscale
 | ||||
| 
 | ||||
|   template<> | ||||
|   class BlenderHelper<GrayscaleTraits, RgbTraits> { | ||||
|     BlendFunc m_blendFunc; | ||||
|   public: | ||||
|     BlenderHelper(Image* dst, const Image* src, const Palette* pal, | ||||
|                   const BlendMode blendMode, const bool newBlend) | ||||
|     { | ||||
|       m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); | ||||
|     } | ||||
| 
 | ||||
|     inline GrayscaleTraits::pixel_t | ||||
|     operator()(GrayscaleTraits::pixel_t dst, | ||||
|                RgbTraits::pixel_t src, | ||||
|                int opacity) | ||||
|     { | ||||
|       // TODO we should be able to configure this function
 | ||||
|       return rgba_to_graya_using_luma(src); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   //////////////////////////////////////////////////////////////////////
 | ||||
|   // X -> Indexed
 | ||||
| 
 | ||||
|   template<> | ||||
|   class BlenderHelper<IndexedTraits, RgbTraits> { | ||||
|     const Palette* m_pal; | ||||
|     BlendMode m_blendMode; | ||||
|     BlendFunc m_blendFunc; | ||||
|     color_t m_maskColor; | ||||
|   public: | ||||
|     BlenderHelper(Image* dst, const Image* src, const Palette* pal, | ||||
|                   const BlendMode blendMode, const bool newBlend) | ||||
|     { | ||||
|       m_blendMode = blendMode; | ||||
|       m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); | ||||
|       m_pal = pal; | ||||
| 
 | ||||
|       if (m_blendMode == BlendMode::SRC) | ||||
|         m_maskColor = -1; | ||||
|       else | ||||
|         m_maskColor = dst->maskColor(); | ||||
|     } | ||||
| 
 | ||||
|     inline IndexedTraits::pixel_t | ||||
|     operator()(IndexedTraits::pixel_t dst, | ||||
|                RgbTraits::pixel_t src, | ||||
|                int opacity) | ||||
|     { | ||||
|       if (dst != m_maskColor) { | ||||
|         src = (*m_blendFunc)(m_pal->getEntry(dst), src, opacity); | ||||
|       } | ||||
|       return m_pal->findBestfit(rgba_getr(src), | ||||
|                                 rgba_getg(src), | ||||
|                                 rgba_getb(src), | ||||
|                                 rgba_geta(src), | ||||
|                                 m_maskColor); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   template<> | ||||
|   class BlenderHelper<IndexedTraits, IndexedTraits> { | ||||
|     BlendMode m_blendMode; | ||||
|     color_t m_maskColor; | ||||
|     int m_paletteSize; | ||||
|   public: | ||||
|     BlenderHelper(Image* dst, const Image* src, const Palette* pal, | ||||
|                   const BlendMode blendMode, const bool newBlend) | ||||
|     { | ||||
|       m_blendMode = blendMode; | ||||
|       m_maskColor = src->maskColor(); | ||||
|       m_paletteSize = pal->size(); | ||||
|     } | ||||
| 
 | ||||
|     inline IndexedTraits::pixel_t | ||||
|     operator()(IndexedTraits::pixel_t dst, | ||||
|                IndexedTraits::pixel_t src, | ||||
|                int opacity) | ||||
|     { | ||||
|       if (m_blendMode == BlendMode::SRC) { | ||||
|         return src; | ||||
|       } | ||||
|       else if (m_blendMode == BlendMode::DST_OVER) { | ||||
|         if (dst != m_maskColor) | ||||
|           return dst; | ||||
|         else | ||||
|           return src; | ||||
|       } | ||||
|       else { | ||||
|         if (src != m_maskColor && src < m_paletteSize) | ||||
|           return src; | ||||
|         else | ||||
|           return dst; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
| } // namespace doc
 | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -72,46 +72,6 @@ void copy_image(Image* dst, const Image* src, int x, int y) | |||
|   dst->copy(src, gfx::Clip(x, y, 0, 0, src->width(), src->height())); | ||||
| } | ||||
| 
 | ||||
| template<typename ImageTraits> | ||||
| void blend_image_templ(Image* dst, | ||||
|                        const Image* src, | ||||
|                        const int x, const int y, | ||||
|                        const int opacity, | ||||
|                        BlendFunc& blender) | ||||
| { | ||||
|   gfx::Clip area = gfx::Clip(x, y, 0, 0, src->width(), src->height()); | ||||
|   if (!area.clip(dst->width(), dst->height(), src->width(), src->height())) | ||||
|     return; | ||||
|   LockImageBits<ImageTraits> dstBits(dst); | ||||
|   const LockImageBits<ImageTraits> srcBits(src); | ||||
|   auto dstIt = dstBits.begin_area(area.dstBounds()); | ||||
|   auto srcIt = srcBits.begin_area(area.srcBounds()); | ||||
|   auto dstEnd = dstBits.end_area(area.dstBounds()); | ||||
|   for (; dstIt < dstEnd; ++dstIt, ++srcIt) | ||||
|     *dstIt = blender(*dstIt, *srcIt, opacity); | ||||
| } | ||||
| 
 | ||||
| void blend_image(Image* dst, const Image* src, const int x, const int y, | ||||
|                  const int opacity, | ||||
|                  const doc::BlendMode blendMode) | ||||
| { | ||||
|   ASSERT(dst->pixelFormat() == src->pixelFormat()); | ||||
|   BlendFunc blender; | ||||
|   switch (src->pixelFormat()) { | ||||
|     case IMAGE_RGB: | ||||
|       blender = get_rgba_blender(blendMode, true); | ||||
|       return blend_image_templ<RgbTraits>(dst, src, x, y, opacity, blender); | ||||
|     case IMAGE_GRAYSCALE: | ||||
|       blender = get_graya_blender(blendMode, true); | ||||
|       return blend_image_templ<GrayscaleTraits>(dst, src, x, y, opacity, blender); | ||||
|     case IMAGE_INDEXED: | ||||
|       blender = get_indexed_blender(blendMode, true); | ||||
|       return blend_image_templ<IndexedTraits>(dst, src, x, y, opacity, blender); | ||||
|     case IMAGE_TILEMAP: | ||||
|       return copy_image(dst, src, x, y); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void copy_image(Image* dst, const Image* src, const gfx::Region& rgn) | ||||
| { | ||||
|   for (const gfx::Rect& rc : rgn) | ||||
|  |  | |||
|  | @ -10,7 +10,6 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "base/ints.h" | ||||
| #include "doc/blend_mode.h" | ||||
| #include "doc/color.h" | ||||
| #include "doc/image_buffer.h" | ||||
| #include "gfx/fwd.h" | ||||
|  | @ -18,7 +17,6 @@ | |||
| namespace doc { | ||||
|   class Brush; | ||||
|   class Image; | ||||
|   class Palette; | ||||
|   class Remap; | ||||
| 
 | ||||
|   color_t get_pixel(const Image* image, int x, int y); | ||||
|  | @ -28,8 +26,6 @@ namespace doc { | |||
| 
 | ||||
|   void copy_image(Image* dst, const Image* src); | ||||
|   void copy_image(Image* dst, const Image* src, int x, int y); | ||||
|   void blend_image(Image* dst, const Image* src, int x, int y, | ||||
|                    const int opacity, const doc::BlendMode blendMode); | ||||
|   void copy_image(Image* dst, const Image* src, const gfx::Region& rgn); | ||||
|   Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr()); | ||||
|   Image* crop_image(const Image* image, const gfx::Rect& bounds, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr()); | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| // Aseprite Render Library
 | ||||
| // Copyright (C) 2019-2023  Igara Studio S.A.
 | ||||
| // Copyright (C) 2019-2024  Igara Studio S.A.
 | ||||
| // Copyright (C) 2001-2018  David Capello
 | ||||
| //
 | ||||
| // This file is released under the terms of the MIT license.
 | ||||
|  | @ -34,119 +34,6 @@ namespace { | |||
| //////////////////////////////////////////////////////////////////////
 | ||||
| // Scaled composite
 | ||||
| 
 | ||||
| template<class DstTraits, class SrcTraits> | ||||
| class BlenderHelper { | ||||
|   BlendFunc m_blendFunc; | ||||
|   color_t m_mask_color; | ||||
| public: | ||||
|   BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) | ||||
|   { | ||||
|     m_blendFunc = SrcTraits::get_blender(blendMode, newBlend); | ||||
|     m_mask_color = src->maskColor(); | ||||
|   } | ||||
|   inline typename DstTraits::pixel_t | ||||
|   operator()(const typename DstTraits::pixel_t& dst, | ||||
|              const typename SrcTraits::pixel_t& src, | ||||
|              const int opacity) | ||||
|   { | ||||
|     if (src != m_mask_color) | ||||
|       return (*m_blendFunc)(dst, src, opacity); | ||||
|     else | ||||
|       return dst; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| template<> | ||||
| class BlenderHelper<RgbTraits, GrayscaleTraits> { | ||||
|   BlendFunc m_blendFunc; | ||||
|   color_t m_mask_color; | ||||
| public: | ||||
|   BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) | ||||
|   { | ||||
|     m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); | ||||
|     m_mask_color = src->maskColor(); | ||||
|   } | ||||
|   inline RgbTraits::pixel_t | ||||
|   operator()(const RgbTraits::pixel_t& dst, | ||||
|              const GrayscaleTraits::pixel_t& src, | ||||
|              const int opacity) | ||||
|   { | ||||
|     if (src != m_mask_color) { | ||||
|       int v = graya_getv(src); | ||||
|       return (*m_blendFunc)(dst, rgba(v, v, v, graya_geta(src)), opacity); | ||||
|     } | ||||
|     else | ||||
|       return dst; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| template<> | ||||
| class BlenderHelper<RgbTraits, IndexedTraits> { | ||||
|   const Palette* m_pal; | ||||
|   BlendMode m_blendMode; | ||||
|   BlendFunc m_blendFunc; | ||||
|   color_t m_mask_color; | ||||
| public: | ||||
|   BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) | ||||
|   { | ||||
|     m_blendMode = blendMode; | ||||
|     m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); | ||||
|     m_mask_color = src->maskColor(); | ||||
|     m_pal = pal; | ||||
|   } | ||||
|   inline RgbTraits::pixel_t | ||||
|   operator()(const RgbTraits::pixel_t& dst, | ||||
|              const IndexedTraits::pixel_t& src, | ||||
|              const int opacity) | ||||
|   { | ||||
|     if (m_blendMode == BlendMode::SRC) { | ||||
|       return m_pal->getEntry(src); | ||||
|     } | ||||
|     else { | ||||
|       if (src != m_mask_color) { | ||||
|         return (*m_blendFunc)(dst, m_pal->getEntry(src), opacity); | ||||
|       } | ||||
|       else | ||||
|         return dst; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| template<> | ||||
| class BlenderHelper<IndexedTraits, IndexedTraits> { | ||||
|   BlendMode m_blendMode; | ||||
|   color_t m_maskColor; | ||||
|   int m_paletteSize; | ||||
| public: | ||||
|   BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) | ||||
|   { | ||||
|     m_blendMode = blendMode; | ||||
|     m_maskColor = src->maskColor(); | ||||
|     m_paletteSize = pal->size(); | ||||
|   } | ||||
|   inline IndexedTraits::pixel_t | ||||
|   operator()(const IndexedTraits::pixel_t& dst, | ||||
|              const IndexedTraits::pixel_t& src, | ||||
|              const int opacity) | ||||
|   { | ||||
|     if (m_blendMode == BlendMode::SRC) { | ||||
|       return src; | ||||
|     } | ||||
|     else if (m_blendMode == BlendMode::DST_OVER) { | ||||
|       if (dst != m_maskColor) | ||||
|         return dst; | ||||
|       else | ||||
|         return src; | ||||
|     } | ||||
|     else { | ||||
|       if (src != m_maskColor && src < m_paletteSize) | ||||
|         return src; | ||||
|       else | ||||
|         return dst; | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| template<class DstTraits, class SrcTraits> | ||||
| void composite_image_without_scale( | ||||
|   Image* dst, const Image* src, const Palette* pal, | ||||
|  | @ -163,7 +50,7 @@ void composite_image_without_scale( | |||
|   ASSERT(DstTraits::pixel_format == dst->pixelFormat()); | ||||
|   ASSERT(SrcTraits::pixel_format == src->pixelFormat()); | ||||
| 
 | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(dst, src, pal, blendMode, newBlend); | ||||
| 
 | ||||
|   gfx::Clip area(areaF); | ||||
|   if (!area.clip(dst->width(), dst->height(), | ||||
|  | @ -226,7 +113,7 @@ void composite_image_scale_up( | |||
|                  int(sy*double(src->height())))) | ||||
|     return; | ||||
| 
 | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(dst, src, pal, blendMode, newBlend); | ||||
|   int px_x, px_y; | ||||
|   int px_w = int(sx); | ||||
|   int px_h = int(sy); | ||||
|  | @ -379,7 +266,7 @@ void composite_image_scale_down( | |||
|                  int(sy*double(src->height())))) | ||||
|     return; | ||||
| 
 | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(dst, src, pal, blendMode, newBlend); | ||||
|   int step_w = int(1.0 / sx); | ||||
|   int step_h = int(1.0 / sy); | ||||
|   if (step_w < 1 || step_h < 1) | ||||
|  | @ -447,7 +334,7 @@ void composite_image_general( | |||
|                  sx*src->width(), sy*src->height())) | ||||
|     return; | ||||
| 
 | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(dst, src, pal, blendMode, newBlend); | ||||
| 
 | ||||
|   gfx::Rect dstBounds( | ||||
|     area.dstBounds().x, area.dstBounds().y, | ||||
|  | @ -522,7 +409,7 @@ void composite_image_general_with_tile_flags( | |||
|                  sx*src->width(), sy*src->height())) | ||||
|     return; | ||||
| 
 | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); | ||||
|   BlenderHelper<DstTraits, SrcTraits> blender(dst, src, pal, blendMode, newBlend); | ||||
| 
 | ||||
|   gfx::Rect dstBounds( | ||||
|     area.dstBounds().x, area.dstBounds().y, | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| -- Copyright (C) 2019-2023  Igara Studio S.A. | ||||
| -- Copyright (C) 2019-2024  Igara Studio S.A. | ||||
| -- Copyright (C) 2018  David Capello | ||||
| -- | ||||
| -- This file is released under the terms of the MIT license. | ||||
|  | @ -330,13 +330,14 @@ do | |||
|                     1, 2, 4, 3, 4, | ||||
|                     3, 4, 0, 0, 0 }) | ||||
| 
 | ||||
|     -- BlendMode.NORMAL by default, so mask color (color=0) is skipped | ||||
|     b:drawImage(a, Point(0, 3)) | ||||
|     expect_img(b, { 0, 0, 0, 0, 0, | ||||
|                     0, 1, 2, 1, 2, | ||||
|                     1, 2, 4, 3, 4, | ||||
|                     0, 1, 2, 0, 0 }) | ||||
|                     3, 1, 2, 0, 0 }) | ||||
| 
 | ||||
|     b:drawImage(a, Point(0, 3)) -- Do nothing | ||||
|     b:drawImage(a, Point(0, 3), 255, BlendMode.SRC) | ||||
|     expect_img(b, { 0, 0, 0, 0, 0, | ||||
|                     0, 1, 2, 1, 2, | ||||
|                     1, 2, 4, 3, 4, | ||||
|  | @ -476,3 +477,60 @@ local spr = Sprite(3, 3)   -- Test with sprite (with transactions & undo/redo) | |||
| test_image_flip(app.image) | ||||
| app.sprite = nil           -- Test without sprite (without transactions) | ||||
| test_image_flip(Image(3, 3)) | ||||
| 
 | ||||
| ---------------------------------------------------------------------- | ||||
| -- Test crash using Image:drawImage() with different color modes | ||||
| 
 | ||||
| do | ||||
|   local tmp = Sprite(3, 3) | ||||
|   local pal = Palette(4) | ||||
|   pal:setColor(0, Color(0, 0, 0)) | ||||
|   pal:setColor(1, Color(255, 0, 0)) | ||||
|   pal:setColor(2, Color(0, 255, 0)) | ||||
|   pal:setColor(3, Color(0, 0, 255)) | ||||
|   tmp:setPalette(pal) | ||||
| 
 | ||||
|   local rgb = Image{ width=2, height=2, colorMode=ColorMode.RGB } | ||||
|   local idx = Image{ width=2, height=2, colorMode=ColorMode.INDEXED } | ||||
| 
 | ||||
|   -- Draw INDEXED -> RGB | ||||
| 
 | ||||
|   array_to_pixels({ 0, 1, | ||||
|                     2, 3 }, idx) | ||||
|   rgb:drawImage(idx) | ||||
| 
 | ||||
|   local k = pal:getColor(0).rgbaPixel | ||||
|   local r = pal:getColor(1).rgbaPixel | ||||
|   local g = pal:getColor(2).rgbaPixel | ||||
|   local b = pal:getColor(3).rgbaPixel | ||||
|   expect_img(rgb, { 0, r, | ||||
|                     g, b }) | ||||
| 
 | ||||
|   rgb:drawImage(idx, 0, 0, 255, BlendMode.SRC) | ||||
|   expect_img(rgb, { k, r, | ||||
|                     g, b }) | ||||
| 
 | ||||
|   rgb:drawImage(idx, 1, 0) | ||||
|   expect_img(rgb, { k, r, | ||||
|                     g, g }) | ||||
| 
 | ||||
|   rgb:drawImage(idx, 1, 0, 255, BlendMode.SRC) | ||||
|   expect_img(rgb, { k, k, | ||||
|                     g, g }) | ||||
| 
 | ||||
|   -- Draw RGB -> INDEXED | ||||
| 
 | ||||
|   array_to_pixels({ 0, r, | ||||
|                     g, b }, rgb) | ||||
| 
 | ||||
|   idx:clear(1) | ||||
|   idx:drawImage(rgb, 0, 0, 255, BlendMode.SRC) | ||||
|   expect_img(idx, { 0, 1, | ||||
|                     2, 3 }) | ||||
| 
 | ||||
|   idx:clear(1) | ||||
|   idx:drawImage(rgb) | ||||
|   expect_img(idx, { 1, 1, | ||||
|                     2, 3 }) | ||||
| 
 | ||||
| end | ||||
|  |  | |||
|  | @ -20,7 +20,22 @@ local function dump_img(image) | |||
|   for v=0,h-1 do | ||||
|     local lineStr = '  ' | ||||
|     for u=0,w-1 do | ||||
|       lineStr = lineStr .. image:getPixel(u, v) .. ',' | ||||
|       local pix = image:getPixel(u, v) | ||||
|       local pixStr | ||||
|       if image.colorMode == ColorMode.RGB then | ||||
|         pixStr = string.format('rgba(%d,%d,%d,%d)', | ||||
|                                app.pixelColor.rgbaR(pix), | ||||
|                                app.pixelColor.rgbaG(pix), | ||||
|                                app.pixelColor.rgbaB(pix), | ||||
|                                app.pixelColor.rgbaA(pix)) | ||||
|       elseif image.colorMode == ColorMode.GRAY then | ||||
|         pixStr = string.format('gray(%d,%d)', | ||||
|                                app.pixelColor.grayaV(pix), | ||||
|                                app.pixelColor.grayaA(pix)) | ||||
|       else | ||||
|         pixStr = tostring(pix) | ||||
|       end | ||||
|       lineStr = lineStr .. pixStr .. ',' | ||||
|     end | ||||
|     print(lineStr) | ||||
|   end | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue