diff --git a/src/app/app_brushes.cpp b/src/app/app_brushes.cpp index fd70a186f..a328ca282 100644 --- a/src/app/app_brushes.cpp +++ b/src/app/app_brushes.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020-2022 Igara Studio S.A. +// Copyright (C) 2020-2023 Igara Studio S.A. // Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of @@ -128,7 +128,7 @@ void save_xml_image(TiXmlElement* imageElem, const Image* image) imageElem->SetAttribute("format", format.c_str()); base::buffer data; - data.reserve(h * image->getRowStrideSize()); + data.reserve(h * image->widthBytes()); switch (image->pixelFormat()) { case IMAGE_RGB:{ const LockImageBits pixels(image); diff --git a/src/app/cmd/copy_rect.cpp b/src/app/cmd/copy_rect.cpp index 06d8b5261..b266ce54d 100644 --- a/src/app/cmd/copy_rect.cpp +++ b/src/app/cmd/copy_rect.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2023 Igara Studio S.A. // Copyright (C) 2001-2015 David Capello // // This program is distributed under the terms of @@ -28,7 +29,7 @@ CopyRect::CopyRect(Image* dst, const Image* src, const gfx::Clip& clip) // Fill m_data with "src" data - int lineSize = src->getRowStrideSize(m_clip.size.w); + int lineSize = src->bytesPerPixel() * m_clip.size.w; m_data.resize(lineSize * m_clip.size.h); auto it = m_data.begin(); @@ -82,7 +83,7 @@ void CopyRect::swap() int CopyRect::lineSize() { - return image()->getRowStrideSize(m_clip.size.w); + return image()->bytesPerPixel() * m_clip.size.w; } } // namespace cmd diff --git a/src/app/file/ase_format.cpp b/src/app/file/ase_format.cpp index f34358f92..dc70724e3 100644 --- a/src/app/file/ase_format.cpp +++ b/src/app/file/ase_format.cpp @@ -110,9 +110,7 @@ public: m_image->height()); } int getScanlineSize() const override { - return doc::calculate_rowstride_bytes( - m_image->pixelFormat(), - m_image->width()); + return m_image->widthBytes(); } const uint8_t* getScanlineAddress(int y) const override { return m_image->getPixelAddress(0, y); @@ -128,9 +126,8 @@ public: m_tileset->grid().tileSize().h * m_tileset->size()); } int getScanlineSize() const override { - return doc::calculate_rowstride_bytes( - m_tileset->sprite()->pixelFormat(), - m_tileset->grid().tileSize().w); + return bytes_per_pixel_for_colormode(m_tileset->sprite()->colorMode()) + * m_tileset->grid().tileSize().w; } const uint8_t* getScanlineAddress(int y) const override { const int h = m_tileset->grid().tileSize().h; diff --git a/src/app/file/fli_format.cpp b/src/app/file/fli_format.cpp index 607047998..a9e83bef8 100644 --- a/src/app/file/fli_format.cpp +++ b/src/app/file/fli_format.cpp @@ -104,7 +104,7 @@ bool FliFormat::onLoad(FileOp* fop) flic::Frame fliFrame; flic::Colormap oldFliColormap; fliFrame.pixels = bmp->getPixelAddress(0, 0); - fliFrame.rowstride = IndexedTraits::getRowStrideBytes(bmp->width()); + fliFrame.rowstride = bmp->rowBytes(); frame_t frame_out = 0; for (frame_t frame_in=0; @@ -229,7 +229,7 @@ bool FliFormat::onSave(FileOp* fop) // Write frame by frame flic::Frame fliFrame; fliFrame.pixels = bmp->getPixelAddress(0, 0); - fliFrame.rowstride = IndexedTraits::getRowStrideBytes(bmp->width()); + fliFrame.rowstride = bmp->rowBytes(); auto frame_beg = fop->roi().selectedFrames().begin(); auto frame_end = fop->roi().selectedFrames().end(); diff --git a/src/app/file/tga_format.cpp b/src/app/file/tga_format.cpp index b17dac52c..57087729e 100644 --- a/src/app/file/tga_format.cpp +++ b/src/app/file/tga_format.cpp @@ -174,8 +174,8 @@ bool TgaFormat::onLoad(FileOp* fop) tga::Image tgaImage; tgaImage.pixels = image->getPixelAddress(0, 0); - tgaImage.rowstride = image->getRowStrideSize(); - tgaImage.bytesPerPixel = image->getRowStrideSize(1); + tgaImage.rowstride = image->rowBytes(); + tgaImage.bytesPerPixel = image->bytesPerPixel(); // Read image TgaDelegate delegate(fop); @@ -312,8 +312,8 @@ bool TgaFormat::onSave(FileOp* fop) doc::ImageRef image = img->getScaledImage(); tga::Image tgaImage; tgaImage.pixels = image->getPixelAddress(0, 0); - tgaImage.rowstride = image->getRowStrideSize(); - tgaImage.bytesPerPixel = image->getRowStrideSize(1); + tgaImage.rowstride = image->rowBytes(); + tgaImage.bytesPerPixel = image->bytesPerPixel(); TgaDelegate delegate(fop); encoder.writeImage(header, tgaImage); diff --git a/src/app/file/webp_format.cpp b/src/app/file/webp_format.cpp index e2f2ee6e9..ecd2b4fc4 100644 --- a/src/app/file/webp_format.cpp +++ b/src/app/file/webp_format.cpp @@ -180,8 +180,11 @@ bool WebPFormat::onLoad(FileOp* fop) Cel* cel = layer->cel(f); if (cel) { - memcpy(cel->image()->getPixelAddress(0, 0), - frame_rgba, h*w*sizeof(uint32_t)); + const uint32_t* src = (const uint32_t*)frame_rgba; + for (int y=0; yimage()->getPixelAddress(0, y), + src, w*sizeof(uint32_t)); + } if (!has_alpha) { const uint32_t* src = (const uint32_t*)frame_rgba; @@ -310,7 +313,7 @@ bool WebPFormat::onSave(FileOp* fop) pic.height = h; pic.use_argb = true; pic.argb = (uint32_t*)image->getPixelAddress(0, 0); - pic.argb_stride = w; + pic.argb_stride = image->rowPixels(); // Stride in pixels (not bytes) pic.user_data = &wd; pic.progress_hook = progress_report; diff --git a/src/app/render/shader_renderer.cpp b/src/app/render/shader_renderer.cpp index e6e77c097..832f9a335 100644 --- a/src/app/render/shader_renderer.cpp +++ b/src/app/render/shader_renderer.cpp @@ -418,7 +418,7 @@ void ShaderRenderer::drawImage(SkCanvas* canvas, { auto skData = SkData::MakeWithoutCopy( (const void*)srcImage->getPixelAddress(0, 0), - srcImage->getMemSize()); + srcImage->rowBytes() * srcImage->height()); switch (srcImage->colorMode()) { @@ -429,7 +429,7 @@ void ShaderRenderer::drawImage(SkCanvas* canvas, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType), skData, - srcImage->getRowStrideSize()); + srcImage->rowBytes()); SkPaint p; p.setAlpha(opacity); @@ -450,7 +450,7 @@ void ShaderRenderer::drawImage(SkCanvas* canvas, kR8G8_unorm_SkColorType, kOpaque_SkAlphaType), skData, - srcImage->getRowStrideSize()); + srcImage->rowBytes()); SkRuntimeShaderBuilder builder(m_grayscaleEffect); builder.child("iImg") = skImg->makeRawShader(SkSamplingOptions(SkFilterMode::kNearest)); @@ -478,7 +478,7 @@ void ShaderRenderer::drawImage(SkCanvas* canvas, kAlpha_8_SkColorType, kUnpremul_SkAlphaType), skData, - srcImage->getRowStrideSize()); + srcImage->rowBytes()); // Use the palette data as an "width x height" image where // width=number of palette colors, and height=1 diff --git a/src/app/script/image_class.cpp b/src/app/script/image_class.cpp index 3de17f352..39fe2eaf4 100644 --- a/src/app/script/image_class.cpp +++ b/src/app/script/image_class.cpp @@ -617,21 +617,29 @@ int Image_get_version(lua_State* L) int Image_get_rowStride(lua_State* L) { const auto obj = get_obj(L, 1); - lua_pushinteger(L, obj->image(L)->getRowStrideSize()); + lua_pushinteger(L, obj->image(L)->rowBytes()); + return 1; +} + +int Image_get_bytesPerPixel(lua_State* L) +{ + const auto obj = get_obj(L, 1); + lua_pushinteger(L, obj->image(L)->bytesPerPixel()); return 1; } int Image_get_bytes(lua_State* L) { const auto img = get_obj(L, 1)->image(L); - lua_pushlstring(L, (const char*)img->getPixelAddress(0, 0), img->getRowStrideSize() * img->height()); + lua_pushlstring(L, (const char*)img->getPixelAddress(0, 0), + img->rowBytes() * img->height()); return 1; } int Image_set_bytes(lua_State* L) { const auto img = get_obj(L, 1)->image(L); - size_t bytes_size, bytes_needed = img->getRowStrideSize() * img->height(); + size_t bytes_size, bytes_needed = img->rowBytes() * img->height(); const char* bytes = lua_tolstring(L, 2, &bytes_size); if (bytes_size == bytes_needed) { @@ -711,6 +719,7 @@ const Property Image_properties[] = { { "id", Image_get_id, nullptr }, { "version", Image_get_version, nullptr }, { "rowStride", Image_get_rowStride, nullptr }, + { "bytesPerPixel", Image_get_bytesPerPixel, nullptr }, { "bytes", Image_get_bytes, Image_set_bytes }, { "width", Image_get_width, nullptr }, { "height", Image_get_height, nullptr }, diff --git a/src/app/util/buffer_region.cpp b/src/app/util/buffer_region.cpp index b4db7ae4b..02ceaad0e 100644 --- a/src/app/util/buffer_region.cpp +++ b/src/app/util/buffer_region.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2023 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -24,7 +24,7 @@ void save_image_region_in_buffer( base::buffer& buffer) { // Calculate buffer size for the region - const size_t bytesPerPixel = image->getRowStrideSize(1); + const size_t bytesPerPixel = image->bytesPerPixel(); size_t reqBytes = 0; for (const auto& rc : region) reqBytes += bytesPerPixel*rc.w*rc.h; @@ -48,7 +48,7 @@ void swap_image_region_with_buffer( doc::Image* image, base::buffer& buffer) { - const size_t bytesPerPixel = image->getRowStrideSize(1); + const size_t bytesPerPixel = image->bytesPerPixel(); auto it = buffer.begin(); for (const auto& rc : region) { for (int y=0; yheight(); spec.bits_per_pixel = 32; spec.bytes_per_row = (image->pixelFormat() == doc::IMAGE_RGB ? - image->getRowStrideSize(): 4*spec.width); + image->rowBytes(): 4*spec.width); spec.red_mask = doc::rgba_r_mask; spec.green_mask = doc::rgba_g_mask; spec.blue_mask = doc::rgba_b_mask; diff --git a/src/desktop/osx/thumbnail.mm b/src/desktop/osx/thumbnail.mm index f5d102bad..56f4f720f 100644 --- a/src/desktop/osx/thumbnail.mm +++ b/src/desktop/osx/thumbnail.mm @@ -153,7 +153,7 @@ CGImageRef get_thumbnail(CFURLRef url, CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); CGContextRef gc = CGBitmapContextCreate( image->getPixelAddress(0, 0), - w, h, 8, image->getRowStrideSize(), cs, + w, h, 8, image->rowBytes(), cs, kCGImageAlphaPremultipliedLast); CGColorSpaceRelease(cs); diff --git a/src/dio/aseprite_decoder.cpp b/src/dio/aseprite_decoder.cpp index a686984a9..f1ce5643c 100644 --- a/src/dio/aseprite_decoder.cpp +++ b/src/dio/aseprite_decoder.cpp @@ -717,8 +717,8 @@ void read_compressed_image_templ(FileInterface* f, throw base::Exception("ZLib error %d in inflateInit().", err); const int width = image->width(); - const int rowstride = ImageTraits::getRowStrideBytes(width); - std::vector scanline(rowstride); + const int widthBytes = image->widthBytes(); + std::vector scanline(widthBytes); std::vector compressed(4096); std::vector uncompressed(4096); int scanline_offset = 0; @@ -774,7 +774,7 @@ void read_compressed_image_templ(FileInterface* f, scanline_offset += n; i += n; } - else if (scanline_offset < rowstride) { + else if (scanline_offset < widthBytes) { // The scanline is not filled yet. break; } diff --git a/src/doc/algorithm/shrink_bounds.cpp b/src/doc/algorithm/shrink_bounds.cpp index db55a64eb..d4e591c5a 100644 --- a/src/doc/algorithm/shrink_bounds.cpp +++ b/src/doc/algorithm/shrink_bounds.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2019-2020 Igara Studio S.A. +// Copyright (c) 2019-2023 Igara Studio S.A. // Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. @@ -60,13 +60,13 @@ bool is_same_pixel(color_t pixel1, color_t pixel2) } template -bool shrink_bounds_left_templ(const Image* image, gfx::Rect& bounds, color_t refpixel, int rowSize) +bool shrink_bounds_left_templ(const Image* image, gfx::Rect& bounds, color_t refpixel, int rowPixels) { int u, v; // Shrink left side for (u=bounds.x; u(image, u, v=bounds.y); - for (; v(image, u, v)); if (!is_same_pixel(*ptr, refpixel)) return (!bounds.isEmpty()); @@ -78,13 +78,13 @@ bool shrink_bounds_left_templ(const Image* image, gfx::Rect& bounds, color_t ref } template -bool shrink_bounds_right_templ(const Image* image, gfx::Rect& bounds, color_t refpixel, int rowSize) +bool shrink_bounds_right_templ(const Image* image, gfx::Rect& bounds, color_t refpixel, int rowPixels) { int u, v; // Shrink right side for (u=bounds.x2()-1; u>=bounds.x; --u) { auto ptr = get_pixel_address_fast(image, u, v=bounds.y); - for (; v(image, u, v)); if (!is_same_pixel(*ptr, refpixel)) return (!bounds.isEmpty()); @@ -133,7 +133,7 @@ template bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel) { // Pixels per row - const int rowSize = image->getRowStrideSize() / image->getRowStrideSize(1); + const int rowPixels = image->rowPixels(); const int canvasSize = image->width()*image->height(); if ((std::thread::hardware_concurrency() >= 4) && ((image->pixelFormat() == IMAGE_RGB && canvasSize >= 800*800) || @@ -144,8 +144,8 @@ bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel // TODO use a base::thread_pool and a base::task for each border - std::thread left ([&]{ shrink_bounds_left_templ (image, leftBounds, refpixel, rowSize); }); - std::thread right ([&]{ shrink_bounds_right_templ (image, rightBounds, refpixel, rowSize); }); + std::thread left ([&]{ shrink_bounds_left_templ (image, leftBounds, refpixel, rowPixels); }); + std::thread right ([&]{ shrink_bounds_right_templ (image, rightBounds, refpixel, rowPixels); }); std::thread top ([&]{ shrink_bounds_top_templ (image, topBounds, refpixel); }); std::thread bottom([&]{ shrink_bounds_bottom_templ(image, bottomBounds, refpixel); }); left.join(); @@ -160,8 +160,8 @@ bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel } else { return - shrink_bounds_left_templ(image, bounds, refpixel, rowSize) && - shrink_bounds_right_templ(image, bounds, refpixel, rowSize) && + shrink_bounds_left_templ(image, bounds, refpixel, rowPixels) && + shrink_bounds_right_templ(image, bounds, refpixel, rowPixels) && shrink_bounds_top_templ(image, bounds, refpixel) && shrink_bounds_bottom_templ(image, bounds, refpixel); } diff --git a/src/doc/aligned_memory.h b/src/doc/aligned_memory.h new file mode 100644 index 000000000..8b156555a --- /dev/null +++ b/src/doc/aligned_memory.h @@ -0,0 +1,27 @@ +// Aseprite Document Library +// Copyright (c) 2023 Igara Studio S.A. +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef DOC_ALIGNED_MEMORY_H_INCLUDED +#pragma once + +#include "base/memory.h" + +// Enable DOC_USE_ALIGNED_PIXELS in case that you want to start using +// SIMD optimizations. Probably more testing is required as the +// program was not well-suited for a rowstride > image width. +//#define DOC_USE_ALIGNED_PIXELS 1 + +#if DOC_USE_ALIGNED_PIXELS + #define doc_align_size(size) (base_align_size(size)) + #define doc_aligned_alloc(size) base_aligned_alloc(size) + #define doc_aligned_free(ptr) base_aligned_free(ptr) +#else + #define doc_align_size(size) (size) + #define doc_aligned_alloc(size) malloc(size) + #define doc_aligned_free(ptr) free(ptr) +#endif + +#endif diff --git a/src/doc/color_mode.h b/src/doc/color_mode.h index 3878f7642..439806267 100644 --- a/src/doc/color_mode.h +++ b/src/doc/color_mode.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2019 Igara Studio S.A. +// Copyright (c) 2019-2023 Igara Studio S.A. // Copyright (c) 2001-2014 David Capello // // This file is released under the terms of the MIT license. @@ -19,6 +19,17 @@ namespace doc { TILEMAP, }; + inline constexpr int bytes_per_pixel_for_colormode(ColorMode cm) { + switch (cm) { + case ColorMode::RGB: return 4; // RgbTraits::bytes_per_pixel + case ColorMode::GRAYSCALE: return 2; // GrayscaleTraits::bytes_per_pixel + case ColorMode::INDEXED: return 1; // IndexedTraits::bytes_per_pixel + case ColorMode::BITMAP: return 1; // BitmapTraits::bytes_per_pixel + case ColorMode::TILEMAP: return 4; // TilemapTraits::bytes_per_pixel + } + return 0; + } + } // namespace doc #endif diff --git a/src/doc/image.cpp b/src/doc/image.cpp index e34a8a93e..1df408123 100644 --- a/src/doc/image.cpp +++ b/src/doc/image.cpp @@ -32,17 +32,7 @@ Image::~Image() int Image::getMemSize() const { - return sizeof(Image) + getRowStrideSize()*height(); -} - -int Image::getRowStrideSize() const -{ - return getRowStrideSize(width()); -} - -int Image::getRowStrideSize(int pixels_per_row) const -{ - return calculate_rowstride_bytes(pixelFormat(), pixels_per_row); + return sizeof(Image) + rowBytes()*height(); } // static diff --git a/src/doc/image.h b/src/doc/image.h index 62dbf38a1..46d1849ad 100644 --- a/src/doc/image.h +++ b/src/doc/image.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2018-2020 Igara Studio S.A. +// Copyright (c) 2018-2023 Igara Studio S.A. // Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. @@ -55,9 +55,22 @@ namespace doc { void setMaskColor(color_t c) { m_spec.setMaskColor(c); } void setColorSpace(const gfx::ColorSpaceRef& cs) { m_spec.setColorSpace(cs); } + // Number of bytes to store one pixel of this image. + int bytesPerPixel() const { return m_spec.bytesPerPixel(); } + + // Number of bytes to store all visible pixels on each row. + int widthBytes() const { return m_spec.widthBytes(); } + + // Number of bytes for each row of this image on memory (some + // bytes for each row might be hidden/just for alignment to + // "base_alignment"). + int rowBytes() const { return m_rowBytes; } + + // Number of pixels for each row (some of these pixels are hidden + // when width() < rowPixels()). + int rowPixels() const { return m_rowBytes / bytesPerPixel(); } + virtual int getMemSize() const override; - int getRowStrideSize() const; - int getRowStrideSize(int pixels_per_row) const; template ImageBits lockBits(LockType lockType, const gfx::Rect& bounds) { @@ -89,30 +102,13 @@ namespace doc { protected: Image(const ImageSpec& spec); + // Number of bytes for each row. + size_t m_rowBytes; + private: ImageSpec m_spec; }; } // namespace doc -// It's here because it needs a complete definition of Image class, -// and then ImageTraits are used in the next functions below. -#include "doc/image_traits.h" - -namespace doc { - - inline int calculate_rowstride_bytes(PixelFormat pixelFormat, int pixels_per_row) - { - switch (pixelFormat) { - case IMAGE_RGB: return RgbTraits::getRowStrideBytes(pixels_per_row); - case IMAGE_GRAYSCALE: return GrayscaleTraits::getRowStrideBytes(pixels_per_row); - case IMAGE_INDEXED: return IndexedTraits::getRowStrideBytes(pixels_per_row); - case IMAGE_BITMAP: return BitmapTraits::getRowStrideBytes(pixels_per_row); - case IMAGE_TILEMAP: return TilemapTraits::getRowStrideBytes(pixels_per_row); - } - return 0; - } - -} // namespace doc - #endif diff --git a/src/doc/image_buffer.h b/src/doc/image_buffer.h index 1a70dc9d6..30ed99e44 100644 --- a/src/doc/image_buffer.h +++ b/src/doc/image_buffer.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2001-2016 David Capello // // This file is released under the terms of the MIT license. @@ -9,32 +9,52 @@ #define DOC_IMAGE_BUFFER_H_INCLUDED #pragma once +#include "base/disable_copying.h" #include "base/ints.h" +#include "doc/aligned_memory.h" +#include #include +#include #include -#include namespace doc { class ImageBuffer { public: - ImageBuffer(std::size_t size = 1) : m_buffer(size) { + ImageBuffer(std::size_t size = 1) + : m_size(doc_align_size(size)) + , m_buffer((uint8_t*)doc_aligned_alloc(m_size)) { } - std::size_t size() const { return m_buffer.size(); } - uint8_t* buffer() { return &m_buffer[0]; } + ~ImageBuffer() noexcept { + if (m_buffer) + doc_aligned_free(m_buffer); + } + + std::size_t size() const { return m_size; } + uint8_t* buffer() { return (uint8_t*)m_buffer; } void resizeIfNecessary(std::size_t size) { - if (size > m_buffer.size()) - m_buffer.resize(size); + if (size > m_size) { + if (m_buffer) { + doc_aligned_free(m_buffer); + m_buffer = nullptr; + } + + m_size = doc_align_size(size); + m_buffer = (uint8_t*)doc_aligned_alloc(m_size); + } } private: - std::vector m_buffer; + size_t m_size; + uint8_t* m_buffer; + + DISABLE_COPYING(ImageBuffer); }; - typedef std::shared_ptr ImageBufferPtr; + using ImageBufferPtr = std::shared_ptr; } // namespace doc diff --git a/src/doc/image_impl.h b/src/doc/image_impl.h index 15ad69376..5f12b1430 100644 --- a/src/doc/image_impl.h +++ b/src/doc/image_impl.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (C) 2018-2021 Igara Studio S.A. +// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2001-2016 David Capello // // This file is released under the terms of the MIT license. @@ -25,22 +25,16 @@ namespace doc { template class ImageImpl : public Image { - private: - typedef typename Traits::address_t address_t; - typedef typename Traits::const_address_t const_address_t; + public: + using traits_t = Traits; + using address_t = typename traits_t::address_t; + using const_address_t = typename traits_t::const_address_t; + private: ImageBufferPtr m_buffer; address_t m_bits; address_t* m_rows; - inline address_t getBitsAddress() { - return m_bits; - } - - inline const_address_t getBitsAddress() const { - return m_bits; - } - inline address_t getLineAddress(int y) { ASSERT(y >= 0 && y < height()); return m_rows[y]; @@ -53,7 +47,12 @@ namespace doc { public: inline address_t address(int x, int y) const { - return (address_t)(getLineAddress(y) + x / (Traits::pixels_per_byte == 0 ? 1 : Traits::pixels_per_byte)); + if constexpr (Traits::pixels_per_byte == 0) { + return (address_t)(getLineAddress(y) + x); + } + else { + return (address_t)(getLineAddress(y) + x / Traits::pixels_per_byte); + } } ImageImpl(const ImageSpec& spec, @@ -63,9 +62,11 @@ namespace doc { { ASSERT(Traits::color_mode == spec.colorMode()); - std::size_t for_rows = sizeof(address_t) * spec.height(); - std::size_t rowstride_bytes = Traits::getRowStrideBytes(spec.width()); - std::size_t required_size = for_rows + rowstride_bytes*spec.height(); + m_rowBytes = Traits::rowstride_bytes(width()); + + const std::size_t for_rows = sizeof(address_t) * height(); + const std::size_t for_pixels = m_rowBytes * height(); + const std::size_t required_size = for_pixels + for_rows; if (!m_buffer) m_buffer = std::make_shared(required_size); @@ -75,13 +76,13 @@ namespace doc { std::fill(m_buffer->buffer(), m_buffer->buffer()+required_size, 0); - m_rows = (address_t*)m_buffer->buffer(); - m_bits = (address_t)(m_buffer->buffer() + for_rows); + m_bits = (address_t)m_buffer->buffer(); + m_rows = (address_t*)(m_buffer->buffer() + for_pixels); address_t addr = m_bits; - for (int y=0; y inline void ImageImpl::clear(color_t color) { - std::fill(getBitsAddress(), - getBitsAddress() + width()*height(), - color); + uint8_t* p = address(0, 0); + std::fill(p, p+rowBytes()*height(), color); } template<> inline void ImageImpl::clear(color_t color) { - std::fill(getBitsAddress(), - getBitsAddress() + BitmapTraits::getRowStrideBytes(width()) * height(), - (color ? 0xff: 0x00)); + uint8_t* p = address(0, 0); + std::fill(p, p+rowBytes()*height(), (color ? 0xff: 0x00)); } template<> diff --git a/src/doc/image_io.cpp b/src/doc/image_io.cpp index 3dc96609d..f118db1e9 100644 --- a/src/doc/image_io.cpp +++ b/src/doc/image_io.cpp @@ -36,11 +36,13 @@ bool write_image(std::ostream& os, const Image* image, CancelIO* cancel) write16(os, image->height()); // Height write32(os, image->maskColor()); // Mask color - int rowSize = image->getRowStrideSize(); + // Number of bytes for visible pixels on each row + const int widthBytes = image->widthBytes(); + #if 0 { for (int c=0; cheight(); c++) - os.write((char*)image->getPixelAddress(0, c), rowSize); + os.write((char*)image->getPixelAddress(0, c), widthBytes); } #else { @@ -65,7 +67,7 @@ bool write_image(std::ostream& os, const Image* image, CancelIO* cancel) } zstream.next_in = (Bytef*)image->getPixelAddress(0, y); - zstream.avail_in = rowSize; + zstream.avail_in = widthBytes; int flush = (y == image->height()-1 ? Z_FINISH: Z_NO_FLUSH); do { @@ -119,12 +121,13 @@ Image* read_image(std::istream& is, bool setId) std::unique_ptr image( Image::create(static_cast(pixelFormat), width, height)); - int rowSize = image->getRowStrideSize(); + + const int widthBytes = image->widthBytes(); #if 0 { for (int c=0; cheight(); c++) - is.read((char*)image->getPixelAddress(0, c), rowSize); + is.read((char*)image->getPixelAddress(0, c), widthBytes); } #else { @@ -139,13 +142,13 @@ Image* read_image(std::istream& is, bool setId) if (err != Z_OK) throw base::Exception("ZLib error %d in inflateInit().", err); - int uncompressed_size = image->height() * rowSize; int uncompressed_offset = 0; int remain = avail_bytes; std::vector compressed(4096); - uint8_t* address = image->getPixelAddress(0, 0); - uint8_t* address_end = image->getPixelAddress(0, 0) + uncompressed_size; + int y = 0; + uint8_t* address = nullptr; + uint8_t* address_end = nullptr; while (remain > 0) { int len = std::min(remain, int(compressed.size())); @@ -166,6 +169,14 @@ Image* read_image(std::istream& is, bool setId) zstream.avail_in = (uInt)bytes_read; do { + if (address == address_end) { + if (y == image->height()) + throw base::Exception("Too much data to uncompress for the image."); + + address = image->getPixelAddress(0, y++); + address_end = address + widthBytes; + } + zstream.next_out = (Bytef*)address; zstream.avail_out = address_end - address; @@ -175,9 +186,6 @@ Image* read_image(std::istream& is, bool setId) int uncompressed_bytes = (int)((address_end - address) - zstream.avail_out); if (uncompressed_bytes > 0) { - if (uncompressed_offset+uncompressed_bytes > uncompressed_size) - throw base::Exception("Bad compressed image."); - uncompressed_offset += uncompressed_bytes; address += uncompressed_bytes; } diff --git a/src/doc/image_spec.h b/src/doc/image_spec.h index a6a14bcb9..d77ba67db 100644 --- a/src/doc/image_spec.h +++ b/src/doc/image_spec.h @@ -40,6 +40,14 @@ namespace doc { gfx::Rect bounds() const { return gfx::Rect(m_size); } const gfx::ColorSpaceRef& colorSpace() const { return m_colorSpace; } + int bytesPerPixel() const { + return bytes_per_pixel_for_colormode(m_colorMode); + } + + int widthBytes() const { + return bytesPerPixel() * width(); + } + // The transparent color for colored images (0 by default) or just 0 for RGBA and Grayscale color_t maskColor() const { return m_maskColor; } diff --git a/src/doc/image_traits.h b/src/doc/image_traits.h index 00fb6b3d5..1691337fd 100644 --- a/src/doc/image_traits.h +++ b/src/doc/image_traits.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2018-2019 Igara Studio S.A. +// Copyright (c) 2018-2023 Igara Studio S.A. // Copyright (c) 2001-2015 David Capello // // This file is released under the terms of the MIT license. @@ -9,6 +9,8 @@ #define DOC_IMAGE_TRAITS_H_INCLUDED #pragma once +#include "base/memory.h" +#include "doc/aligned_memory.h" #include "doc/blend_funcs.h" #include "doc/color.h" #include "doc/color_mode.h" @@ -35,10 +37,14 @@ namespace doc { static const pixel_t min_value = 0x00000000l; static const pixel_t max_value = 0xffffffffl; - static inline int getRowStrideBytes(int pixels_per_row) { + static inline int width_bytes(int pixels_per_row) { return bytes_per_pixel * pixels_per_row; } + static inline int rowstride_bytes(int pixels_per_row) { + return doc_align_size(width_bytes(pixels_per_row)); + } + static inline BlendFunc get_blender(BlendMode blend_mode, bool newBlend) { return get_rgba_blender(blend_mode, newBlend); } @@ -76,10 +82,14 @@ namespace doc { static const pixel_t min_value = 0x0000; static const pixel_t max_value = 0xffff; - static inline int getRowStrideBytes(int pixels_per_row) { + static inline int width_bytes(int pixels_per_row) { return bytes_per_pixel * pixels_per_row; } + static inline int rowstride_bytes(int pixels_per_row) { + return doc_align_size(width_bytes(pixels_per_row)); + } + static inline BlendFunc get_blender(BlendMode blend_mode, bool newBlend) { return get_graya_blender(blend_mode, newBlend); } @@ -117,10 +127,14 @@ namespace doc { static const pixel_t min_value = 0x00; static const pixel_t max_value = 0xff; - static inline int getRowStrideBytes(int pixels_per_row) { + static inline int width_bytes(int pixels_per_row) { return bytes_per_pixel * pixels_per_row; } + static inline int rowstride_bytes(int pixels_per_row) { + return doc_align_size(width_bytes(pixels_per_row)); + } + static inline BlendFunc get_blender(BlendMode blend_mode, bool newBlend) { return get_indexed_blender(blend_mode, newBlend); } @@ -149,8 +163,12 @@ namespace doc { static const pixel_t min_value = 0; static const pixel_t max_value = 1; - static inline int getRowStrideBytes(int pixels_per_row) { - return ((pixels_per_row+7) / 8); + static inline int width_bytes(int pixels_per_row) { + return (pixels_per_row+7) / 8; + } + + static inline int rowstride_bytes(int pixels_per_row) { + return doc_align_size(width_bytes(pixels_per_row)); } static inline bool same_color(const pixel_t a, const pixel_t b) { @@ -177,10 +195,14 @@ namespace doc { static const pixel_t min_value = 0x00000000l; static const pixel_t max_value = 0xffffffffl; - static inline int getRowStrideBytes(int pixels_per_row) { + static inline int width_bytes(int pixels_per_row) { return bytes_per_pixel * pixels_per_row; } + static inline int rowstride_bytes(int pixels_per_row) { + return doc_align_size(width_bytes(pixels_per_row)); + } + static inline BlendFunc get_blender(BlendMode blend_mode, bool newBlend) { return get_indexed_blender(blend_mode, newBlend); } diff --git a/src/doc/mask_io.cpp b/src/doc/mask_io.cpp index 36dbad98f..f44221f49 100644 --- a/src/doc/mask_io.cpp +++ b/src/doc/mask_io.cpp @@ -1,4 +1,5 @@ // Aseprite Document Library +// Copyright (c) 2023 Igara Studio S.A. // Copyright (c) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -11,6 +12,7 @@ #include "doc/mask_io.h" #include "base/serialization.h" +#include "doc/image_traits.h" #include "doc/mask.h" #include @@ -39,7 +41,7 @@ void write_mask(std::ostream& os, const Mask* mask) write16(os, mask->bitmap() ? bounds.h: 0); // Height if (mask->bitmap()) { - int size = BitmapTraits::getRowStrideBytes(bounds.w); + int size = BitmapTraits::width_bytes(bounds.w); for (int c=0; cbitmap()->getPixelAddress(0, c), size); @@ -56,7 +58,7 @@ Mask* read_mask(std::istream& is) std::unique_ptr mask(new Mask()); if (w > 0 && h > 0) { - int size = BitmapTraits::getRowStrideBytes(w); + int size = BitmapTraits::width_bytes(w); mask->add(gfx::Rect(x, y, w, h)); for (int c=0; cbounds().h; c++) diff --git a/src/doc/primitives.cpp b/src/doc/primitives.cpp index 9e7387227..73a484786 100644 --- a/src/doc/primitives.cpp +++ b/src/doc/primitives.cpp @@ -13,6 +13,7 @@ #include "doc/algo.h" #include "doc/brush.h" +#include "doc/dispatch.h" #include "doc/image_impl.h" #include "doc/palette.h" #include "doc/remap.h" @@ -24,6 +25,10 @@ #include +#if defined(__x86_64__) || defined(_WIN64) + #include +#endif + namespace doc { color_t get_pixel(const Image* image, int x, int y) @@ -378,7 +383,7 @@ int count_diff_between_images_templ(const Image* i1, const Image* i2) } template -int is_same_image_templ(const Image* i1, const Image* i2) +bool is_same_image_templ(const Image* i1, const Image* i2) { const LockImageBits bits1(i1); const LockImageBits bits2(i2); @@ -394,6 +399,77 @@ int is_same_image_templ(const Image* i1, const Image* i2) return true; } +template +bool is_same_image_simd_templ(const Image* i1, const Image* i2) +{ + using address_t = typename ImageTraits::address_t; + const int w = i1->width(); + const int h = i1->height(); + for (int y=0; ygetPixelAddress(0, y); + auto q = (const address_t)i2->getPixelAddress(0, y); + int x = 0; + +#if DOC_USE_ALIGNED_PIXELS +#if defined(__x86_64__) || defined(_WIN64) + // Use SSE2 + + if constexpr (ImageTraits::bytes_per_pixel == 4) { + for (; x+4<=w; x+=4, p+=4, q+=4) { + __m128i r = _mm_cmpeq_epi32(*(const __m128i*)p, *(const __m128i*)q); + if (_mm_movemask_epi8(r) != 0xffff) { // !_mm_test_all_ones(r) + if (!ImageTraits::same_color(p[0], q[0]) || + !ImageTraits::same_color(p[1], q[1]) || + !ImageTraits::same_color(p[2], q[2]) || + !ImageTraits::same_color(p[3], q[3])) + return false; + } + } + } + else if constexpr (ImageTraits::bytes_per_pixel == 2) { + for (; x+8<=w; x+=8, p+=8, q+=8) { + __m128i r = _mm_cmpeq_epi16(*(const __m128i*)p, *(const __m128i*)q); + if (_mm_movemask_epi8(r) != 0xffff) { // !_mm_test_all_ones(r) + if (!ImageTraits::same_color(p[0], q[0]) || + !ImageTraits::same_color(p[1], q[1]) || + !ImageTraits::same_color(p[2], q[2]) || + !ImageTraits::same_color(p[3], q[3]) || + !ImageTraits::same_color(p[4], q[4]) || + !ImageTraits::same_color(p[5], q[5]) || + !ImageTraits::same_color(p[6], q[6]) || + !ImageTraits::same_color(p[7], q[7])) + return false; + } + } + } + else if constexpr (ImageTraits::bytes_per_pixel == 1) { + for (; x+16<=w; x+=16, p+=16, q+=16) { + __m128i r = _mm_cmpeq_epi8(*(const __m128i*)p, *(const __m128i*)q); + if (_mm_movemask_epi8(r) != 0xffff) { // !_mm_test_all_ones(r) + return false; + } + } + } +#endif +#endif // DOC_USE_ALIGNED_PIXELS + { + for (; x+4<=w; x+=4, p+=4, q+=4) { + if (!ImageTraits::same_color(p[0], q[0]) || + !ImageTraits::same_color(p[1], q[1]) || + !ImageTraits::same_color(p[2], q[2]) || + !ImageTraits::same_color(p[3], q[3])) + return false; + } + } + + for (; xpixelFormat() != i2->pixelFormat()) || + if ((i1->colorMode() != i2->colorMode()) || (i1->width() != i2->width()) || (i1->height() != i2->height())) return false; - switch (i1->pixelFormat()) { - case IMAGE_RGB: return is_same_image_templ(i1, i2); - case IMAGE_GRAYSCALE: return is_same_image_templ(i1, i2); - case IMAGE_INDEXED: return is_same_image_templ(i1, i2); - case IMAGE_BITMAP: return is_same_image_templ(i1, i2); - case IMAGE_TILEMAP: return is_same_image_templ(i1, i2); - } + DOC_DISPATCH_BY_COLOR_MODE( + i1->colorMode(), + is_same_image_templ, + i1, i2); + + ASSERT(false); + return false; +} + +bool is_same_image(const Image* i1, const Image* i2) +{ + const ColorMode cm = i1->colorMode(); + + if ((cm != i2->colorMode()) || + (i1->width() != i2->width()) || + (i1->height() != i2->height())) + return false; + + if (cm == ColorMode::BITMAP) + return is_same_image_templ(i1, i2); + + DOC_DISPATCH_BY_COLOR_MODE_EXCLUDE_BITMAP( + cm, + is_same_image_simd_templ, + i1, i2); ASSERT(false); return false; @@ -499,19 +593,18 @@ static uint32_t calculate_image_hash_templ(const Image* image, static_assert(sizeof(void*) == 4, "This CPU is not 32-bit"); #endif - const uint32_t rowlen = ImageTraits::getRowStrideBytes(bounds.w); - const uint32_t len = rowlen * bounds.h; - if (bounds == image->bounds()) { + const uint32_t widthBytes = ImageTraits::bytes_per_pixel * bounds.w; + const uint32_t len = widthBytes * bounds.h; + if (bounds == image->bounds() && + widthBytes == image->rowBytes()) { return CITYHASH((const char*)image->getPixelAddress(0, 0), len); } else { - ASSERT(false); // TODO not used at this moment - std::vector buf(len); uint8_t* dst = &buf[0]; - for (int y=0; ygetPixelAddress(bounds.x, bounds.y+y); - std::copy(dst, dst+rowlen, src); + for (int y=0; ygetPixelAddress(bounds.x, bounds.y+y); + std::copy(src, src+widthBytes, dst); } return CITYHASH((const char*)&buf[0], buf.size()); } diff --git a/src/doc/primitives.h b/src/doc/primitives.h index fa1a64cb5..29a0b19b0 100644 --- a/src/doc/primitives.h +++ b/src/doc/primitives.h @@ -50,6 +50,7 @@ namespace doc { int count_diff_between_images(const Image* i1, const Image* i2); bool is_same_image(const Image* i1, const Image* i2); + bool is_same_image_slow(const Image* i1, const Image* i2); void remap_image(Image* image, const Remap& remap); diff --git a/src/doc/primitives_benchmark.cpp b/src/doc/primitives_benchmark.cpp new file mode 100644 index 000000000..3f0d57674 --- /dev/null +++ b/src/doc/primitives_benchmark.cpp @@ -0,0 +1,63 @@ +// Aseprite Document Library +// Copyright (c) 2023 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/primitives.h" + +#include "doc/algorithm/random_image.h" +#include "doc/image_ref.h" + +#include + +using namespace doc; + +void BM_IsSameImageOld(benchmark::State& state) { + const auto pf = (PixelFormat)state.range(0); + const int w = state.range(1); + const int h = state.range(2); + ImageRef a(Image::create(pf, w, h)); + doc::algorithm::random_image(a.get()); + ImageRef b(Image::createCopy(a.get())); + while (state.KeepRunning()) { + is_same_image_slow(a.get(), b.get()); + } +} + +void BM_IsSameImageNew(benchmark::State& state) { + const auto pf = (PixelFormat)state.range(0); + const int w = state.range(1); + const int h = state.range(2); + ImageRef a(Image::create(pf, w, h)); + doc::algorithm::random_image(a.get()); + ImageRef b(Image::createCopy(a.get())); + while (state.KeepRunning()) { + is_same_image(a.get(), b.get()); + } +} + +#define DEFARGS() \ + ->Args({ IMAGE_RGB, 16, 16 }) \ + ->Args({ IMAGE_RGB, 1024, 1024 }) \ + ->Args({ IMAGE_RGB, 8192, 8192 }) \ + ->Args({ IMAGE_GRAYSCALE, 16, 16 }) \ + ->Args({ IMAGE_GRAYSCALE, 1024, 1024 }) \ + ->Args({ IMAGE_GRAYSCALE, 8192, 8192 }) \ + ->Args({ IMAGE_INDEXED, 16, 16 }) \ + ->Args({ IMAGE_INDEXED, 1024, 1024 }) \ + ->Args({ IMAGE_INDEXED, 8192, 8192 }) + +BENCHMARK(BM_IsSameImageOld) + DEFARGS() + ->UseRealTime(); + +BENCHMARK(BM_IsSameImageNew) + DEFARGS() + ->UseRealTime(); + +BENCHMARK_MAIN(); diff --git a/src/doc/primitives_fast.h b/src/doc/primitives_fast.h index 5de77a674..7970bd7b5 100644 --- a/src/doc/primitives_fast.h +++ b/src/doc/primitives_fast.h @@ -1,4 +1,5 @@ // Aseprite Document Library +// Copyright (c) 2023 Igara Studio S.A. // Copyright (c) 2001-2015 David Capello // // This file is released under the terms of the MIT license. @@ -9,6 +10,7 @@ #pragma once #include "doc/color.h" +#include "doc/image_traits.h" namespace doc { class Image; diff --git a/src/doc/primitives_tests.cpp b/src/doc/primitives_tests.cpp new file mode 100644 index 000000000..6b0427e2e --- /dev/null +++ b/src/doc/primitives_tests.cpp @@ -0,0 +1,87 @@ +// Aseprite Document Library +// Copyright (c) 2023 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 + +#include "doc/primitives.h" + +#include "doc/algorithm/random_image.h" +#include "doc/image_impl.h" +#include "doc/image_ref.h" +#include "doc/primitives_fast.h" + +#include + +//#define FULL_TEST 1 + +using namespace doc; +using namespace gfx; + +template +class Primitives : public testing::Test { +protected: + Primitives() { } +}; + +using ImageAllTraits = testing::Types; +TYPED_TEST_SUITE(Primitives, ImageAllTraits); + +TYPED_TEST(Primitives, IsSameImage) +{ + using ImageTraits = TypeParam; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dist(0, 256); + +#if FULL_TEST + int w = 200; + int h = 200; + { + { +#else + for (int h=2; h<207; h+=5) { + for (int w=2; w<207; w+=5) { +#endif + ImageRef a(Image::create(ImageTraits::pixel_format, w, h)); + doc::algorithm::random_image(a.get()); + + ImageRef b(Image::createCopy(a.get())); +#if FULL_TEST + for (int v=0; v(b.get(), u, v); + if (old != 0) + put_pixel_fast(b.get(), u, v, 0); + else + put_pixel_fast(b.get(), u, v, 1); + + ASSERT_FALSE(is_same_image_slow(a.get(), b.get())); + ASSERT_FALSE(is_same_image(a.get(), b.get())); + + put_pixel_fast(b.get(), u, v, old); + } + } + } +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp index 68623fdb6..13a0e7119 100644 --- a/src/doc/sprite.cpp +++ b/src/doc/sprite.cpp @@ -241,7 +241,7 @@ int Sprite::getMemSize() const std::vector images; getImages(images); for (const ImageRef& image : images) - size += image->getRowStrideSize() * image->height(); + size += image->rowBytes() * image->height(); return size; } diff --git a/tests/scripts/image.lua b/tests/scripts/image.lua index fdf6c659a..fb05a3e9d 100644 --- a/tests/scripts/image.lua +++ b/tests/scripts/image.lua @@ -14,6 +14,7 @@ assert(a.width == 32) assert(a.height == 64) assert(a.colorMode == ColorMode.RGB) -- RGB by default assert(a.rowStride == 32*4) +assert(a.bytesPerPixel == 4) assert(a:isEmpty()) assert(a:isPlain(rgba(0, 0, 0, 0))) assert(a:isPlain(0)) @@ -24,6 +25,7 @@ do assert(b.height == 64) assert(b.colorMode == ColorMode.INDEXED) assert(b.rowStride == 32*1) + assert(b.bytesPerPixel == 1) local c = Image{ width=32, height=64, colorMode=ColorMode.INDEXED } assert(c.width == 32) diff --git a/third_party/benchmark b/third_party/benchmark index 151ead624..02a354f3f 160000 --- a/third_party/benchmark +++ b/third_party/benchmark @@ -1 +1 @@ -Subproject commit 151ead6242b2075b5a3d55905440a3aab245a800 +Subproject commit 02a354f3f323ae8256948e1dc77ddcb1dfc297da