mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-04 13:59:46 +00:00
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:
parent
ce37fbc8e3
commit
aeeef8e255
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 },
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
27
src/doc/aligned_memory.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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<>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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++)
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
63
src/doc/primitives_benchmark.cpp
Normal file
63
src/doc/primitives_benchmark.cpp
Normal 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();
|
@ -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;
|
||||
|
87
src/doc/primitives_tests.cpp
Normal file
87
src/doc/primitives_tests.cpp
Normal 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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
2
third_party/benchmark
vendored
2
third_party/benchmark
vendored
@ -1 +1 @@
|
||||
Subproject commit 151ead6242b2075b5a3d55905440a3aab245a800
|
||||
Subproject commit 02a354f3f323ae8256948e1dc77ddcb1dfc297da
|
Loading…
Reference in New Issue
Block a user