Add suppor for doc::Image row stride size > width size

This patch solves several problems introducing the possibility to
specify a row stride bigger than the width (visible pixels) on each
image row. Useful in case that we want to align the initial pixel
address of each row (if DOC_USE_ALIGNED_PIXELS is defined).

This allows us to use some SIMD intrinsics (e.g. SSE2) for some image
functions in the future (right now implemented only in the new
is_same_image_simd_templ() for is_same_image()).

Anyway to avoid breaking some existing code, by default we'll still
keep the old behavior: row stride bytes = width bytes (so
DOC_USE_ALIGNED_PIXELS is undefined).
This commit is contained in:
David Capello 2023-08-07 15:12:11 -03:00
parent ce37fbc8e3
commit aeeef8e255
31 changed files with 501 additions and 164 deletions

View File

@ -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<RgbTraits> pixels(image);

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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; y<h; ++y, src+=w) {
memcpy(cel->image()->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;

View File

@ -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

View File

@ -617,21 +617,29 @@ int Image_get_version(lua_State* L)
int Image_get_rowStride(lua_State* L)
{
const auto obj = get_obj<ImageObj>(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<ImageObj>(L, 1);
lua_pushinteger(L, obj->image(L)->bytesPerPixel());
return 1;
}
int Image_get_bytes(lua_State* L)
{
const auto img = get_obj<ImageObj>(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<ImageObj>(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 },

View File

@ -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; y<rc.h; ++y) {

View File

@ -131,7 +131,7 @@ bool Clipboard::setNativeBitmap(const doc::Image* image,
spec.height = image->height();
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;

View File

@ -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);

View File

@ -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<uint8_t> scanline(rowstride);
const int widthBytes = image->widthBytes();
std::vector<uint8_t> scanline(widthBytes);
std::vector<uint8_t> compressed(4096);
std::vector<uint8_t> 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;
}

View File

@ -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<BitmapTraits>(color_t pixel1, color_t pixel2)
}
template<typename ImageTraits>
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<bounds.x2(); ++u) {
auto ptr = get_pixel_address_fast<ImageTraits>(image, u, v=bounds.y);
for (; v<bounds.y2(); ++v, ptr+=rowSize) {
for (; v<bounds.y2(); ++v, ptr+=rowPixels) {
ASSERT(ptr == get_pixel_address_fast<ImageTraits>(image, u, v));
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
return (!bounds.isEmpty());
@ -78,13 +78,13 @@ bool shrink_bounds_left_templ(const Image* image, gfx::Rect& bounds, color_t ref
}
template<typename ImageTraits>
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<ImageTraits>(image, u, v=bounds.y);
for (; v<bounds.y2(); ++v, ptr+=rowSize) {
for (; v<bounds.y2(); ++v, ptr+=rowPixels) {
ASSERT(ptr == get_pixel_address_fast<ImageTraits>(image, u, v));
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
return (!bounds.isEmpty());
@ -133,7 +133,7 @@ template<typename ImageTraits>
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 <ImageTraits>(image, leftBounds, refpixel, rowSize); });
std::thread right ([&]{ shrink_bounds_right_templ <ImageTraits>(image, rightBounds, refpixel, rowSize); });
std::thread left ([&]{ shrink_bounds_left_templ <ImageTraits>(image, leftBounds, refpixel, rowPixels); });
std::thread right ([&]{ shrink_bounds_right_templ <ImageTraits>(image, rightBounds, refpixel, rowPixels); });
std::thread top ([&]{ shrink_bounds_top_templ <ImageTraits>(image, topBounds, refpixel); });
std::thread bottom([&]{ shrink_bounds_bottom_templ<ImageTraits>(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<ImageTraits>(image, bounds, refpixel, rowSize) &&
shrink_bounds_right_templ<ImageTraits>(image, bounds, refpixel, rowSize) &&
shrink_bounds_left_templ<ImageTraits>(image, bounds, refpixel, rowPixels) &&
shrink_bounds_right_templ<ImageTraits>(image, bounds, refpixel, rowPixels) &&
shrink_bounds_top_templ<ImageTraits>(image, bounds, refpixel) &&
shrink_bounds_bottom_templ<ImageTraits>(image, bounds, refpixel);
}

27
src/doc/aligned_memory.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<typename ImageTraits>
ImageBits<ImageTraits> 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

View File

@ -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 <algorithm>
#include <cstddef>
#include <cstdlib>
#include <memory>
#include <vector>
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<uint8_t> m_buffer;
size_t m_size;
uint8_t* m_buffer;
DISABLE_COPYING(ImageBuffer);
};
typedef std::shared_ptr<ImageBuffer> ImageBufferPtr;
using ImageBufferPtr = std::shared_ptr<ImageBuffer>;
} // namespace doc

View File

@ -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 Traits>
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<ImageBuffer>(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<spec.height(); ++y) {
for (int y=0; y<height(); ++y) {
m_rows[y] = addr;
addr = (address_t)(((uint8_t*)addr) + rowstride_bytes);
addr = (address_t)(((uint8_t*)addr) + m_rowBytes);
}
}
@ -107,16 +108,12 @@ namespace doc {
}
void clear(color_t color) override {
int w = width();
int h = height();
// Fill the first line
address_t first = address(0, 0);
std::fill(first, first+w, color);
// Copy the first line into all other lines
for (int y=1; y<h; ++y)
std::copy(first, first+w, address(0, y));
const int w = width();
const int h = height();
for (int y=0; y<h; ++y) {
address_t p = address(0, y);
std::fill(p, p+w, color);
}
}
void copy(const Image* _src, gfx::Clip area) override {
@ -225,16 +222,14 @@ namespace doc {
template<>
inline void ImageImpl<IndexedTraits>::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<BitmapTraits>::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<>

View File

@ -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; c<image->height(); 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(
Image::create(static_cast<PixelFormat>(pixelFormat), width, height));
int rowSize = image->getRowStrideSize();
const int widthBytes = image->widthBytes();
#if 0
{
for (int c=0; c<image->height(); 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<uint8_t> 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;
}

View File

@ -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; }

View File

@ -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);
}

View File

@ -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 <iostream>
@ -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; c<bounds.h; c++)
os.write((char*)mask->bitmap()->getPixelAddress(0, c), size);
@ -56,7 +58,7 @@ Mask* read_mask(std::istream& is)
std::unique_ptr<Mask> 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; c<mask->bounds().h; c++)

View File

@ -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 <stdexcept>
#if defined(__x86_64__) || defined(_WIN64)
#include <emmintrin.h>
#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<typename ImageTraits>
int is_same_image_templ(const Image* i1, const Image* i2)
bool is_same_image_templ(const Image* i1, const Image* i2)
{
const LockImageBits<ImageTraits> bits1(i1);
const LockImageBits<ImageTraits> bits2(i2);
@ -394,6 +399,77 @@ int is_same_image_templ(const Image* i1, const Image* i2)
return true;
}
template<typename ImageTraits>
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; y<h; ++y) {
auto p = (const address_t)i1->getPixelAddress(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 (; x<w; ++x, ++p, ++q) {
if (!ImageTraits::same_color(*p, *q))
return false;
}
}
return true;
}
} // anonymous namespace
bool is_plain_image(const Image* img, color_t c)
@ -435,20 +511,38 @@ int count_diff_between_images(const Image* i1, const Image* i2)
return -1;
}
bool is_same_image(const Image* i1, const Image* i2)
bool is_same_image_slow(const Image* i1, const Image* i2)
{
if ((i1->pixelFormat() != 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<RgbTraits>(i1, i2);
case IMAGE_GRAYSCALE: return is_same_image_templ<GrayscaleTraits>(i1, i2);
case IMAGE_INDEXED: return is_same_image_templ<IndexedTraits>(i1, i2);
case IMAGE_BITMAP: return is_same_image_templ<BitmapTraits>(i1, i2);
case IMAGE_TILEMAP: return is_same_image_templ<TilemapTraits>(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<BitmapTraits>(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<uint8_t> buf(len);
uint8_t* dst = &buf[0];
for (int y=0; y<bounds.h; ++y, dst+=rowlen) {
auto src = image->getPixelAddress(bounds.x, bounds.y+y);
std::copy(dst, dst+rowlen, src);
for (int y=0; y<bounds.h; ++y, dst+=widthBytes) {
auto src = (const uint8_t*)image->getPixelAddress(bounds.x, bounds.y+y);
std::copy(src, src+widthBytes, dst);
}
return CITYHASH((const char*)&buf[0], buf.size());
}

View File

@ -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);

View File

@ -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 <benchmark/benchmark.h>
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();

View File

@ -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;

View File

@ -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 <gtest/gtest.h>
#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 <random>
//#define FULL_TEST 1
using namespace doc;
using namespace gfx;
template<typename T>
class Primitives : public testing::Test {
protected:
Primitives() { }
};
using ImageAllTraits = testing::Types<RgbTraits, GrayscaleTraits, IndexedTraits, BitmapTraits, TilemapTraits>;
TYPED_TEST_SUITE(Primitives, ImageAllTraits);
TYPED_TEST(Primitives, IsSameImage)
{
using ImageTraits = TypeParam;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> 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<h; ++v)
for (int u=0; u<w; ++u) {
#else
for (int i = 0; i < 32; ++i) {
int u = dist(gen) % w;
int v = dist(gen) % h;
#endif
ASSERT_TRUE(is_same_image_slow(a.get(), b.get()));
ASSERT_TRUE(is_same_image(a.get(), b.get()));
auto old = get_pixel_fast<ImageTraits>(b.get(), u, v);
if (old != 0)
put_pixel_fast<ImageTraits>(b.get(), u, v, 0);
else
put_pixel_fast<ImageTraits>(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<ImageTraits>(b.get(), u, v, old);
}
}
}
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -241,7 +241,7 @@ int Sprite::getMemSize() const
std::vector<ImageRef> images;
getImages(images);
for (const ImageRef& image : images)
size += image->getRowStrideSize() * image->height();
size += image->rowBytes() * image->height();
return size;
}

View File

@ -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)

@ -1 +1 @@
Subproject commit 151ead6242b2075b5a3d55905440a3aab245a800
Subproject commit 02a354f3f323ae8256948e1dc77ddcb1dfc297da