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
|
// Aseprite
|
||||||
// Copyright (C) 2020-2022 Igara Studio S.A.
|
// Copyright (C) 2020-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2016 David Capello
|
// Copyright (C) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// 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());
|
imageElem->SetAttribute("format", format.c_str());
|
||||||
|
|
||||||
base::buffer data;
|
base::buffer data;
|
||||||
data.reserve(h * image->getRowStrideSize());
|
data.reserve(h * image->widthBytes());
|
||||||
switch (image->pixelFormat()) {
|
switch (image->pixelFormat()) {
|
||||||
case IMAGE_RGB:{
|
case IMAGE_RGB:{
|
||||||
const LockImageBits<RgbTraits> pixels(image);
|
const LockImageBits<RgbTraits> pixels(image);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2015 David Capello
|
// Copyright (C) 2001-2015 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// 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
|
// 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);
|
m_data.resize(lineSize * m_clip.size.h);
|
||||||
|
|
||||||
auto it = m_data.begin();
|
auto it = m_data.begin();
|
||||||
@ -82,7 +83,7 @@ void CopyRect::swap()
|
|||||||
|
|
||||||
int CopyRect::lineSize()
|
int CopyRect::lineSize()
|
||||||
{
|
{
|
||||||
return image()->getRowStrideSize(m_clip.size.w);
|
return image()->bytesPerPixel() * m_clip.size.w;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cmd
|
} // namespace cmd
|
||||||
|
@ -110,9 +110,7 @@ public:
|
|||||||
m_image->height());
|
m_image->height());
|
||||||
}
|
}
|
||||||
int getScanlineSize() const override {
|
int getScanlineSize() const override {
|
||||||
return doc::calculate_rowstride_bytes(
|
return m_image->widthBytes();
|
||||||
m_image->pixelFormat(),
|
|
||||||
m_image->width());
|
|
||||||
}
|
}
|
||||||
const uint8_t* getScanlineAddress(int y) const override {
|
const uint8_t* getScanlineAddress(int y) const override {
|
||||||
return m_image->getPixelAddress(0, y);
|
return m_image->getPixelAddress(0, y);
|
||||||
@ -128,9 +126,8 @@ public:
|
|||||||
m_tileset->grid().tileSize().h * m_tileset->size());
|
m_tileset->grid().tileSize().h * m_tileset->size());
|
||||||
}
|
}
|
||||||
int getScanlineSize() const override {
|
int getScanlineSize() const override {
|
||||||
return doc::calculate_rowstride_bytes(
|
return bytes_per_pixel_for_colormode(m_tileset->sprite()->colorMode())
|
||||||
m_tileset->sprite()->pixelFormat(),
|
* m_tileset->grid().tileSize().w;
|
||||||
m_tileset->grid().tileSize().w);
|
|
||||||
}
|
}
|
||||||
const uint8_t* getScanlineAddress(int y) const override {
|
const uint8_t* getScanlineAddress(int y) const override {
|
||||||
const int h = m_tileset->grid().tileSize().h;
|
const int h = m_tileset->grid().tileSize().h;
|
||||||
|
@ -104,7 +104,7 @@ bool FliFormat::onLoad(FileOp* fop)
|
|||||||
flic::Frame fliFrame;
|
flic::Frame fliFrame;
|
||||||
flic::Colormap oldFliColormap;
|
flic::Colormap oldFliColormap;
|
||||||
fliFrame.pixels = bmp->getPixelAddress(0, 0);
|
fliFrame.pixels = bmp->getPixelAddress(0, 0);
|
||||||
fliFrame.rowstride = IndexedTraits::getRowStrideBytes(bmp->width());
|
fliFrame.rowstride = bmp->rowBytes();
|
||||||
|
|
||||||
frame_t frame_out = 0;
|
frame_t frame_out = 0;
|
||||||
for (frame_t frame_in=0;
|
for (frame_t frame_in=0;
|
||||||
@ -229,7 +229,7 @@ bool FliFormat::onSave(FileOp* fop)
|
|||||||
// Write frame by frame
|
// Write frame by frame
|
||||||
flic::Frame fliFrame;
|
flic::Frame fliFrame;
|
||||||
fliFrame.pixels = bmp->getPixelAddress(0, 0);
|
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_beg = fop->roi().selectedFrames().begin();
|
||||||
auto frame_end = fop->roi().selectedFrames().end();
|
auto frame_end = fop->roi().selectedFrames().end();
|
||||||
|
@ -174,8 +174,8 @@ bool TgaFormat::onLoad(FileOp* fop)
|
|||||||
|
|
||||||
tga::Image tgaImage;
|
tga::Image tgaImage;
|
||||||
tgaImage.pixels = image->getPixelAddress(0, 0);
|
tgaImage.pixels = image->getPixelAddress(0, 0);
|
||||||
tgaImage.rowstride = image->getRowStrideSize();
|
tgaImage.rowstride = image->rowBytes();
|
||||||
tgaImage.bytesPerPixel = image->getRowStrideSize(1);
|
tgaImage.bytesPerPixel = image->bytesPerPixel();
|
||||||
|
|
||||||
// Read image
|
// Read image
|
||||||
TgaDelegate delegate(fop);
|
TgaDelegate delegate(fop);
|
||||||
@ -312,8 +312,8 @@ bool TgaFormat::onSave(FileOp* fop)
|
|||||||
doc::ImageRef image = img->getScaledImage();
|
doc::ImageRef image = img->getScaledImage();
|
||||||
tga::Image tgaImage;
|
tga::Image tgaImage;
|
||||||
tgaImage.pixels = image->getPixelAddress(0, 0);
|
tgaImage.pixels = image->getPixelAddress(0, 0);
|
||||||
tgaImage.rowstride = image->getRowStrideSize();
|
tgaImage.rowstride = image->rowBytes();
|
||||||
tgaImage.bytesPerPixel = image->getRowStrideSize(1);
|
tgaImage.bytesPerPixel = image->bytesPerPixel();
|
||||||
|
|
||||||
TgaDelegate delegate(fop);
|
TgaDelegate delegate(fop);
|
||||||
encoder.writeImage(header, tgaImage);
|
encoder.writeImage(header, tgaImage);
|
||||||
|
@ -180,8 +180,11 @@ bool WebPFormat::onLoad(FileOp* fop)
|
|||||||
|
|
||||||
Cel* cel = layer->cel(f);
|
Cel* cel = layer->cel(f);
|
||||||
if (cel) {
|
if (cel) {
|
||||||
memcpy(cel->image()->getPixelAddress(0, 0),
|
const uint32_t* src = (const uint32_t*)frame_rgba;
|
||||||
frame_rgba, h*w*sizeof(uint32_t));
|
for (int y=0; y<h; ++y, src+=w) {
|
||||||
|
memcpy(cel->image()->getPixelAddress(0, y),
|
||||||
|
src, w*sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
if (!has_alpha) {
|
if (!has_alpha) {
|
||||||
const uint32_t* src = (const uint32_t*)frame_rgba;
|
const uint32_t* src = (const uint32_t*)frame_rgba;
|
||||||
@ -310,7 +313,7 @@ bool WebPFormat::onSave(FileOp* fop)
|
|||||||
pic.height = h;
|
pic.height = h;
|
||||||
pic.use_argb = true;
|
pic.use_argb = true;
|
||||||
pic.argb = (uint32_t*)image->getPixelAddress(0, 0);
|
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.user_data = &wd;
|
||||||
pic.progress_hook = progress_report;
|
pic.progress_hook = progress_report;
|
||||||
|
|
||||||
|
@ -418,7 +418,7 @@ void ShaderRenderer::drawImage(SkCanvas* canvas,
|
|||||||
{
|
{
|
||||||
auto skData = SkData::MakeWithoutCopy(
|
auto skData = SkData::MakeWithoutCopy(
|
||||||
(const void*)srcImage->getPixelAddress(0, 0),
|
(const void*)srcImage->getPixelAddress(0, 0),
|
||||||
srcImage->getMemSize());
|
srcImage->rowBytes() * srcImage->height());
|
||||||
|
|
||||||
switch (srcImage->colorMode()) {
|
switch (srcImage->colorMode()) {
|
||||||
|
|
||||||
@ -429,7 +429,7 @@ void ShaderRenderer::drawImage(SkCanvas* canvas,
|
|||||||
kRGBA_8888_SkColorType,
|
kRGBA_8888_SkColorType,
|
||||||
kUnpremul_SkAlphaType),
|
kUnpremul_SkAlphaType),
|
||||||
skData,
|
skData,
|
||||||
srcImage->getRowStrideSize());
|
srcImage->rowBytes());
|
||||||
|
|
||||||
SkPaint p;
|
SkPaint p;
|
||||||
p.setAlpha(opacity);
|
p.setAlpha(opacity);
|
||||||
@ -450,7 +450,7 @@ void ShaderRenderer::drawImage(SkCanvas* canvas,
|
|||||||
kR8G8_unorm_SkColorType,
|
kR8G8_unorm_SkColorType,
|
||||||
kOpaque_SkAlphaType),
|
kOpaque_SkAlphaType),
|
||||||
skData,
|
skData,
|
||||||
srcImage->getRowStrideSize());
|
srcImage->rowBytes());
|
||||||
|
|
||||||
SkRuntimeShaderBuilder builder(m_grayscaleEffect);
|
SkRuntimeShaderBuilder builder(m_grayscaleEffect);
|
||||||
builder.child("iImg") = skImg->makeRawShader(SkSamplingOptions(SkFilterMode::kNearest));
|
builder.child("iImg") = skImg->makeRawShader(SkSamplingOptions(SkFilterMode::kNearest));
|
||||||
@ -478,7 +478,7 @@ void ShaderRenderer::drawImage(SkCanvas* canvas,
|
|||||||
kAlpha_8_SkColorType,
|
kAlpha_8_SkColorType,
|
||||||
kUnpremul_SkAlphaType),
|
kUnpremul_SkAlphaType),
|
||||||
skData,
|
skData,
|
||||||
srcImage->getRowStrideSize());
|
srcImage->rowBytes());
|
||||||
|
|
||||||
// Use the palette data as an "width x height" image where
|
// Use the palette data as an "width x height" image where
|
||||||
// width=number of palette colors, and height=1
|
// 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)
|
int Image_get_rowStride(lua_State* L)
|
||||||
{
|
{
|
||||||
const auto obj = get_obj<ImageObj>(L, 1);
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Image_get_bytes(lua_State* L)
|
int Image_get_bytes(lua_State* L)
|
||||||
{
|
{
|
||||||
const auto img = get_obj<ImageObj>(L, 1)->image(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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Image_set_bytes(lua_State* L)
|
int Image_set_bytes(lua_State* L)
|
||||||
{
|
{
|
||||||
const auto img = get_obj<ImageObj>(L, 1)->image(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);
|
const char* bytes = lua_tolstring(L, 2, &bytes_size);
|
||||||
|
|
||||||
if (bytes_size == bytes_needed) {
|
if (bytes_size == bytes_needed) {
|
||||||
@ -711,6 +719,7 @@ const Property Image_properties[] = {
|
|||||||
{ "id", Image_get_id, nullptr },
|
{ "id", Image_get_id, nullptr },
|
||||||
{ "version", Image_get_version, nullptr },
|
{ "version", Image_get_version, nullptr },
|
||||||
{ "rowStride", Image_get_rowStride, nullptr },
|
{ "rowStride", Image_get_rowStride, nullptr },
|
||||||
|
{ "bytesPerPixel", Image_get_bytesPerPixel, nullptr },
|
||||||
{ "bytes", Image_get_bytes, Image_set_bytes },
|
{ "bytes", Image_get_bytes, Image_set_bytes },
|
||||||
{ "width", Image_get_width, nullptr },
|
{ "width", Image_get_width, nullptr },
|
||||||
{ "height", Image_get_height, nullptr },
|
{ "height", Image_get_height, nullptr },
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019 Igara Studio S.A.
|
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -24,7 +24,7 @@ void save_image_region_in_buffer(
|
|||||||
base::buffer& buffer)
|
base::buffer& buffer)
|
||||||
{
|
{
|
||||||
// Calculate buffer size for the region
|
// Calculate buffer size for the region
|
||||||
const size_t bytesPerPixel = image->getRowStrideSize(1);
|
const size_t bytesPerPixel = image->bytesPerPixel();
|
||||||
size_t reqBytes = 0;
|
size_t reqBytes = 0;
|
||||||
for (const auto& rc : region)
|
for (const auto& rc : region)
|
||||||
reqBytes += bytesPerPixel*rc.w*rc.h;
|
reqBytes += bytesPerPixel*rc.w*rc.h;
|
||||||
@ -48,7 +48,7 @@ void swap_image_region_with_buffer(
|
|||||||
doc::Image* image,
|
doc::Image* image,
|
||||||
base::buffer& buffer)
|
base::buffer& buffer)
|
||||||
{
|
{
|
||||||
const size_t bytesPerPixel = image->getRowStrideSize(1);
|
const size_t bytesPerPixel = image->bytesPerPixel();
|
||||||
auto it = buffer.begin();
|
auto it = buffer.begin();
|
||||||
for (const auto& rc : region) {
|
for (const auto& rc : region) {
|
||||||
for (int y=0; y<rc.h; ++y) {
|
for (int y=0; y<rc.h; ++y) {
|
||||||
|
@ -131,7 +131,7 @@ bool Clipboard::setNativeBitmap(const doc::Image* image,
|
|||||||
spec.height = image->height();
|
spec.height = image->height();
|
||||||
spec.bits_per_pixel = 32;
|
spec.bits_per_pixel = 32;
|
||||||
spec.bytes_per_row = (image->pixelFormat() == doc::IMAGE_RGB ?
|
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.red_mask = doc::rgba_r_mask;
|
||||||
spec.green_mask = doc::rgba_g_mask;
|
spec.green_mask = doc::rgba_g_mask;
|
||||||
spec.blue_mask = doc::rgba_b_mask;
|
spec.blue_mask = doc::rgba_b_mask;
|
||||||
|
@ -153,7 +153,7 @@ CGImageRef get_thumbnail(CFURLRef url,
|
|||||||
CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
||||||
CGContextRef gc = CGBitmapContextCreate(
|
CGContextRef gc = CGBitmapContextCreate(
|
||||||
image->getPixelAddress(0, 0),
|
image->getPixelAddress(0, 0),
|
||||||
w, h, 8, image->getRowStrideSize(), cs,
|
w, h, 8, image->rowBytes(), cs,
|
||||||
kCGImageAlphaPremultipliedLast);
|
kCGImageAlphaPremultipliedLast);
|
||||||
CGColorSpaceRelease(cs);
|
CGColorSpaceRelease(cs);
|
||||||
|
|
||||||
|
@ -717,8 +717,8 @@ void read_compressed_image_templ(FileInterface* f,
|
|||||||
throw base::Exception("ZLib error %d in inflateInit().", err);
|
throw base::Exception("ZLib error %d in inflateInit().", err);
|
||||||
|
|
||||||
const int width = image->width();
|
const int width = image->width();
|
||||||
const int rowstride = ImageTraits::getRowStrideBytes(width);
|
const int widthBytes = image->widthBytes();
|
||||||
std::vector<uint8_t> scanline(rowstride);
|
std::vector<uint8_t> scanline(widthBytes);
|
||||||
std::vector<uint8_t> compressed(4096);
|
std::vector<uint8_t> compressed(4096);
|
||||||
std::vector<uint8_t> uncompressed(4096);
|
std::vector<uint8_t> uncompressed(4096);
|
||||||
int scanline_offset = 0;
|
int scanline_offset = 0;
|
||||||
@ -774,7 +774,7 @@ void read_compressed_image_templ(FileInterface* f,
|
|||||||
scanline_offset += n;
|
scanline_offset += n;
|
||||||
i += n;
|
i += n;
|
||||||
}
|
}
|
||||||
else if (scanline_offset < rowstride) {
|
else if (scanline_offset < widthBytes) {
|
||||||
// The scanline is not filled yet.
|
// The scanline is not filled yet.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// 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
|
// Copyright (c) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// 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>
|
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;
|
int u, v;
|
||||||
// Shrink left side
|
// Shrink left side
|
||||||
for (u=bounds.x; u<bounds.x2(); ++u) {
|
for (u=bounds.x; u<bounds.x2(); ++u) {
|
||||||
auto ptr = get_pixel_address_fast<ImageTraits>(image, u, v=bounds.y);
|
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));
|
ASSERT(ptr == get_pixel_address_fast<ImageTraits>(image, u, v));
|
||||||
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
|
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
|
||||||
return (!bounds.isEmpty());
|
return (!bounds.isEmpty());
|
||||||
@ -78,13 +78,13 @@ bool shrink_bounds_left_templ(const Image* image, gfx::Rect& bounds, color_t ref
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename ImageTraits>
|
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;
|
int u, v;
|
||||||
// Shrink right side
|
// Shrink right side
|
||||||
for (u=bounds.x2()-1; u>=bounds.x; --u) {
|
for (u=bounds.x2()-1; u>=bounds.x; --u) {
|
||||||
auto ptr = get_pixel_address_fast<ImageTraits>(image, u, v=bounds.y);
|
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));
|
ASSERT(ptr == get_pixel_address_fast<ImageTraits>(image, u, v));
|
||||||
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
|
if (!is_same_pixel<ImageTraits>(*ptr, refpixel))
|
||||||
return (!bounds.isEmpty());
|
return (!bounds.isEmpty());
|
||||||
@ -133,7 +133,7 @@ template<typename ImageTraits>
|
|||||||
bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel)
|
bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel)
|
||||||
{
|
{
|
||||||
// Pixels per row
|
// Pixels per row
|
||||||
const int rowSize = image->getRowStrideSize() / image->getRowStrideSize(1);
|
const int rowPixels = image->rowPixels();
|
||||||
const int canvasSize = image->width()*image->height();
|
const int canvasSize = image->width()*image->height();
|
||||||
if ((std::thread::hardware_concurrency() >= 4) &&
|
if ((std::thread::hardware_concurrency() >= 4) &&
|
||||||
((image->pixelFormat() == IMAGE_RGB && canvasSize >= 800*800) ||
|
((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
|
// 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 left ([&]{ shrink_bounds_left_templ <ImageTraits>(image, leftBounds, refpixel, rowPixels); });
|
||||||
std::thread right ([&]{ shrink_bounds_right_templ <ImageTraits>(image, rightBounds, refpixel, rowSize); });
|
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 top ([&]{ shrink_bounds_top_templ <ImageTraits>(image, topBounds, refpixel); });
|
||||||
std::thread bottom([&]{ shrink_bounds_bottom_templ<ImageTraits>(image, bottomBounds, refpixel); });
|
std::thread bottom([&]{ shrink_bounds_bottom_templ<ImageTraits>(image, bottomBounds, refpixel); });
|
||||||
left.join();
|
left.join();
|
||||||
@ -160,8 +160,8 @@ bool shrink_bounds_templ(const Image* image, gfx::Rect& bounds, color_t refpixel
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
shrink_bounds_left_templ<ImageTraits>(image, bounds, refpixel, rowSize) &&
|
shrink_bounds_left_templ<ImageTraits>(image, bounds, refpixel, rowPixels) &&
|
||||||
shrink_bounds_right_templ<ImageTraits>(image, bounds, refpixel, rowSize) &&
|
shrink_bounds_right_templ<ImageTraits>(image, bounds, refpixel, rowPixels) &&
|
||||||
shrink_bounds_top_templ<ImageTraits>(image, bounds, refpixel) &&
|
shrink_bounds_top_templ<ImageTraits>(image, bounds, refpixel) &&
|
||||||
shrink_bounds_bottom_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
|
// Aseprite Document Library
|
||||||
// Copyright (c) 2019 Igara Studio S.A.
|
// Copyright (c) 2019-2023 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2014 David Capello
|
// Copyright (c) 2001-2014 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -19,6 +19,17 @@ namespace doc {
|
|||||||
TILEMAP,
|
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
|
} // namespace doc
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -32,17 +32,7 @@ Image::~Image()
|
|||||||
|
|
||||||
int Image::getMemSize() const
|
int Image::getMemSize() const
|
||||||
{
|
{
|
||||||
return sizeof(Image) + getRowStrideSize()*height();
|
return sizeof(Image) + rowBytes()*height();
|
||||||
}
|
|
||||||
|
|
||||||
int Image::getRowStrideSize() const
|
|
||||||
{
|
|
||||||
return getRowStrideSize(width());
|
|
||||||
}
|
|
||||||
|
|
||||||
int Image::getRowStrideSize(int pixels_per_row) const
|
|
||||||
{
|
|
||||||
return calculate_rowstride_bytes(pixelFormat(), pixels_per_row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// 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
|
// Copyright (c) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// 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 setMaskColor(color_t c) { m_spec.setMaskColor(c); }
|
||||||
void setColorSpace(const gfx::ColorSpaceRef& cs) { m_spec.setColorSpace(cs); }
|
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;
|
virtual int getMemSize() const override;
|
||||||
int getRowStrideSize() const;
|
|
||||||
int getRowStrideSize(int pixels_per_row) const;
|
|
||||||
|
|
||||||
template<typename ImageTraits>
|
template<typename ImageTraits>
|
||||||
ImageBits<ImageTraits> lockBits(LockType lockType, const gfx::Rect& bounds) {
|
ImageBits<ImageTraits> lockBits(LockType lockType, const gfx::Rect& bounds) {
|
||||||
@ -89,30 +102,13 @@ namespace doc {
|
|||||||
protected:
|
protected:
|
||||||
Image(const ImageSpec& spec);
|
Image(const ImageSpec& spec);
|
||||||
|
|
||||||
|
// Number of bytes for each row.
|
||||||
|
size_t m_rowBytes;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ImageSpec m_spec;
|
ImageSpec m_spec;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace doc
|
} // 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
|
#endif
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
// Copyright (C) 2019 Igara Studio S.A.
|
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2016 David Capello
|
// Copyright (C) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -9,32 +9,52 @@
|
|||||||
#define DOC_IMAGE_BUFFER_H_INCLUDED
|
#define DOC_IMAGE_BUFFER_H_INCLUDED
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/disable_copying.h"
|
||||||
#include "base/ints.h"
|
#include "base/ints.h"
|
||||||
|
#include "doc/aligned_memory.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace doc {
|
namespace doc {
|
||||||
|
|
||||||
class ImageBuffer {
|
class ImageBuffer {
|
||||||
public:
|
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(); }
|
~ImageBuffer() noexcept {
|
||||||
uint8_t* buffer() { return &m_buffer[0]; }
|
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) {
|
void resizeIfNecessary(std::size_t size) {
|
||||||
if (size > m_buffer.size())
|
if (size > m_size) {
|
||||||
m_buffer.resize(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:
|
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
|
} // namespace doc
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// 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
|
// Copyright (C) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -25,22 +25,16 @@ namespace doc {
|
|||||||
|
|
||||||
template<class Traits>
|
template<class Traits>
|
||||||
class ImageImpl : public Image {
|
class ImageImpl : public Image {
|
||||||
private:
|
public:
|
||||||
typedef typename Traits::address_t address_t;
|
using traits_t = Traits;
|
||||||
typedef typename Traits::const_address_t const_address_t;
|
using address_t = typename traits_t::address_t;
|
||||||
|
using const_address_t = typename traits_t::const_address_t;
|
||||||
|
|
||||||
|
private:
|
||||||
ImageBufferPtr m_buffer;
|
ImageBufferPtr m_buffer;
|
||||||
address_t m_bits;
|
address_t m_bits;
|
||||||
address_t* m_rows;
|
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) {
|
inline address_t getLineAddress(int y) {
|
||||||
ASSERT(y >= 0 && y < height());
|
ASSERT(y >= 0 && y < height());
|
||||||
return m_rows[y];
|
return m_rows[y];
|
||||||
@ -53,7 +47,12 @@ namespace doc {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
inline address_t address(int x, int y) const {
|
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,
|
ImageImpl(const ImageSpec& spec,
|
||||||
@ -63,9 +62,11 @@ namespace doc {
|
|||||||
{
|
{
|
||||||
ASSERT(Traits::color_mode == spec.colorMode());
|
ASSERT(Traits::color_mode == spec.colorMode());
|
||||||
|
|
||||||
std::size_t for_rows = sizeof(address_t) * spec.height();
|
m_rowBytes = Traits::rowstride_bytes(width());
|
||||||
std::size_t rowstride_bytes = Traits::getRowStrideBytes(spec.width());
|
|
||||||
std::size_t required_size = for_rows + rowstride_bytes*spec.height();
|
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)
|
if (!m_buffer)
|
||||||
m_buffer = std::make_shared<ImageBuffer>(required_size);
|
m_buffer = std::make_shared<ImageBuffer>(required_size);
|
||||||
@ -75,13 +76,13 @@ namespace doc {
|
|||||||
std::fill(m_buffer->buffer(),
|
std::fill(m_buffer->buffer(),
|
||||||
m_buffer->buffer()+required_size, 0);
|
m_buffer->buffer()+required_size, 0);
|
||||||
|
|
||||||
m_rows = (address_t*)m_buffer->buffer();
|
m_bits = (address_t)m_buffer->buffer();
|
||||||
m_bits = (address_t)(m_buffer->buffer() + for_rows);
|
m_rows = (address_t*)(m_buffer->buffer() + for_pixels);
|
||||||
|
|
||||||
address_t addr = m_bits;
|
address_t addr = m_bits;
|
||||||
for (int y=0; y<spec.height(); ++y) {
|
for (int y=0; y<height(); ++y) {
|
||||||
m_rows[y] = addr;
|
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 {
|
void clear(color_t color) override {
|
||||||
int w = width();
|
const int w = width();
|
||||||
int h = height();
|
const int h = height();
|
||||||
|
for (int y=0; y<h; ++y) {
|
||||||
// Fill the first line
|
address_t p = address(0, y);
|
||||||
address_t first = address(0, 0);
|
std::fill(p, p+w, color);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void copy(const Image* _src, gfx::Clip area) override {
|
void copy(const Image* _src, gfx::Clip area) override {
|
||||||
@ -225,16 +222,14 @@ namespace doc {
|
|||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline void ImageImpl<IndexedTraits>::clear(color_t color) {
|
inline void ImageImpl<IndexedTraits>::clear(color_t color) {
|
||||||
std::fill(getBitsAddress(),
|
uint8_t* p = address(0, 0);
|
||||||
getBitsAddress() + width()*height(),
|
std::fill(p, p+rowBytes()*height(), color);
|
||||||
color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline void ImageImpl<BitmapTraits>::clear(color_t color) {
|
inline void ImageImpl<BitmapTraits>::clear(color_t color) {
|
||||||
std::fill(getBitsAddress(),
|
uint8_t* p = address(0, 0);
|
||||||
getBitsAddress() + BitmapTraits::getRowStrideBytes(width()) * height(),
|
std::fill(p, p+rowBytes()*height(), (color ? 0xff: 0x00));
|
||||||
(color ? 0xff: 0x00));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -36,11 +36,13 @@ bool write_image(std::ostream& os, const Image* image, CancelIO* cancel)
|
|||||||
write16(os, image->height()); // Height
|
write16(os, image->height()); // Height
|
||||||
write32(os, image->maskColor()); // Mask color
|
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
|
#if 0
|
||||||
{
|
{
|
||||||
for (int c=0; c<image->height(); c++)
|
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
|
#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.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);
|
int flush = (y == image->height()-1 ? Z_FINISH: Z_NO_FLUSH);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@ -119,12 +121,13 @@ Image* read_image(std::istream& is, bool setId)
|
|||||||
|
|
||||||
std::unique_ptr<Image> image(
|
std::unique_ptr<Image> image(
|
||||||
Image::create(static_cast<PixelFormat>(pixelFormat), width, height));
|
Image::create(static_cast<PixelFormat>(pixelFormat), width, height));
|
||||||
int rowSize = image->getRowStrideSize();
|
|
||||||
|
const int widthBytes = image->widthBytes();
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
{
|
{
|
||||||
for (int c=0; c<image->height(); c++)
|
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
|
#else
|
||||||
{
|
{
|
||||||
@ -139,13 +142,13 @@ Image* read_image(std::istream& is, bool setId)
|
|||||||
if (err != Z_OK)
|
if (err != Z_OK)
|
||||||
throw base::Exception("ZLib error %d in inflateInit().", err);
|
throw base::Exception("ZLib error %d in inflateInit().", err);
|
||||||
|
|
||||||
int uncompressed_size = image->height() * rowSize;
|
|
||||||
int uncompressed_offset = 0;
|
int uncompressed_offset = 0;
|
||||||
int remain = avail_bytes;
|
int remain = avail_bytes;
|
||||||
|
|
||||||
std::vector<uint8_t> compressed(4096);
|
std::vector<uint8_t> compressed(4096);
|
||||||
uint8_t* address = image->getPixelAddress(0, 0);
|
int y = 0;
|
||||||
uint8_t* address_end = image->getPixelAddress(0, 0) + uncompressed_size;
|
uint8_t* address = nullptr;
|
||||||
|
uint8_t* address_end = nullptr;
|
||||||
|
|
||||||
while (remain > 0) {
|
while (remain > 0) {
|
||||||
int len = std::min(remain, int(compressed.size()));
|
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;
|
zstream.avail_in = (uInt)bytes_read;
|
||||||
|
|
||||||
do {
|
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.next_out = (Bytef*)address;
|
||||||
zstream.avail_out = address_end - 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);
|
int uncompressed_bytes = (int)((address_end - address) - zstream.avail_out);
|
||||||
if (uncompressed_bytes > 0) {
|
if (uncompressed_bytes > 0) {
|
||||||
if (uncompressed_offset+uncompressed_bytes > uncompressed_size)
|
|
||||||
throw base::Exception("Bad compressed image.");
|
|
||||||
|
|
||||||
uncompressed_offset += uncompressed_bytes;
|
uncompressed_offset += uncompressed_bytes;
|
||||||
address += uncompressed_bytes;
|
address += uncompressed_bytes;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,14 @@ namespace doc {
|
|||||||
gfx::Rect bounds() const { return gfx::Rect(m_size); }
|
gfx::Rect bounds() const { return gfx::Rect(m_size); }
|
||||||
const gfx::ColorSpaceRef& colorSpace() const { return m_colorSpace; }
|
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
|
// The transparent color for colored images (0 by default) or just 0 for RGBA and Grayscale
|
||||||
color_t maskColor() const { return m_maskColor; }
|
color_t maskColor() const { return m_maskColor; }
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// 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
|
// Copyright (c) 2001-2015 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -9,6 +9,8 @@
|
|||||||
#define DOC_IMAGE_TRAITS_H_INCLUDED
|
#define DOC_IMAGE_TRAITS_H_INCLUDED
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/memory.h"
|
||||||
|
#include "doc/aligned_memory.h"
|
||||||
#include "doc/blend_funcs.h"
|
#include "doc/blend_funcs.h"
|
||||||
#include "doc/color.h"
|
#include "doc/color.h"
|
||||||
#include "doc/color_mode.h"
|
#include "doc/color_mode.h"
|
||||||
@ -35,10 +37,14 @@ namespace doc {
|
|||||||
static const pixel_t min_value = 0x00000000l;
|
static const pixel_t min_value = 0x00000000l;
|
||||||
static const pixel_t max_value = 0xffffffffl;
|
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;
|
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) {
|
static inline BlendFunc get_blender(BlendMode blend_mode, bool newBlend) {
|
||||||
return get_rgba_blender(blend_mode, 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 min_value = 0x0000;
|
||||||
static const pixel_t max_value = 0xffff;
|
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;
|
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) {
|
static inline BlendFunc get_blender(BlendMode blend_mode, bool newBlend) {
|
||||||
return get_graya_blender(blend_mode, 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 min_value = 0x00;
|
||||||
static const pixel_t max_value = 0xff;
|
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;
|
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) {
|
static inline BlendFunc get_blender(BlendMode blend_mode, bool newBlend) {
|
||||||
return get_indexed_blender(blend_mode, 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 min_value = 0;
|
||||||
static const pixel_t max_value = 1;
|
static const pixel_t max_value = 1;
|
||||||
|
|
||||||
static inline int getRowStrideBytes(int pixels_per_row) {
|
static inline int width_bytes(int pixels_per_row) {
|
||||||
return ((pixels_per_row+7) / 8);
|
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) {
|
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 min_value = 0x00000000l;
|
||||||
static const pixel_t max_value = 0xffffffffl;
|
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;
|
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) {
|
static inline BlendFunc get_blender(BlendMode blend_mode, bool newBlend) {
|
||||||
return get_indexed_blender(blend_mode, newBlend);
|
return get_indexed_blender(blend_mode, newBlend);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
|
// Copyright (c) 2023 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2018 David Capello
|
// Copyright (c) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -11,6 +12,7 @@
|
|||||||
#include "doc/mask_io.h"
|
#include "doc/mask_io.h"
|
||||||
|
|
||||||
#include "base/serialization.h"
|
#include "base/serialization.h"
|
||||||
|
#include "doc/image_traits.h"
|
||||||
#include "doc/mask.h"
|
#include "doc/mask.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -39,7 +41,7 @@ void write_mask(std::ostream& os, const Mask* mask)
|
|||||||
write16(os, mask->bitmap() ? bounds.h: 0); // Height
|
write16(os, mask->bitmap() ? bounds.h: 0); // Height
|
||||||
|
|
||||||
if (mask->bitmap()) {
|
if (mask->bitmap()) {
|
||||||
int size = BitmapTraits::getRowStrideBytes(bounds.w);
|
int size = BitmapTraits::width_bytes(bounds.w);
|
||||||
|
|
||||||
for (int c=0; c<bounds.h; c++)
|
for (int c=0; c<bounds.h; c++)
|
||||||
os.write((char*)mask->bitmap()->getPixelAddress(0, c), size);
|
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());
|
std::unique_ptr<Mask> mask(new Mask());
|
||||||
|
|
||||||
if (w > 0 && h > 0) {
|
if (w > 0 && h > 0) {
|
||||||
int size = BitmapTraits::getRowStrideBytes(w);
|
int size = BitmapTraits::width_bytes(w);
|
||||||
|
|
||||||
mask->add(gfx::Rect(x, y, w, h));
|
mask->add(gfx::Rect(x, y, w, h));
|
||||||
for (int c=0; c<mask->bounds().h; c++)
|
for (int c=0; c<mask->bounds().h; c++)
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include "doc/algo.h"
|
#include "doc/algo.h"
|
||||||
#include "doc/brush.h"
|
#include "doc/brush.h"
|
||||||
|
#include "doc/dispatch.h"
|
||||||
#include "doc/image_impl.h"
|
#include "doc/image_impl.h"
|
||||||
#include "doc/palette.h"
|
#include "doc/palette.h"
|
||||||
#include "doc/remap.h"
|
#include "doc/remap.h"
|
||||||
@ -24,6 +25,10 @@
|
|||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#if defined(__x86_64__) || defined(_WIN64)
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace doc {
|
namespace doc {
|
||||||
|
|
||||||
color_t get_pixel(const Image* image, int x, int y)
|
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>
|
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> bits1(i1);
|
||||||
const LockImageBits<ImageTraits> bits2(i2);
|
const LockImageBits<ImageTraits> bits2(i2);
|
||||||
@ -394,6 +399,77 @@ int is_same_image_templ(const Image* i1, const Image* i2)
|
|||||||
return true;
|
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
|
} // anonymous namespace
|
||||||
|
|
||||||
bool is_plain_image(const Image* img, color_t c)
|
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;
|
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->width() != i2->width()) ||
|
||||||
(i1->height() != i2->height()))
|
(i1->height() != i2->height()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
switch (i1->pixelFormat()) {
|
DOC_DISPATCH_BY_COLOR_MODE(
|
||||||
case IMAGE_RGB: return is_same_image_templ<RgbTraits>(i1, i2);
|
i1->colorMode(),
|
||||||
case IMAGE_GRAYSCALE: return is_same_image_templ<GrayscaleTraits>(i1, i2);
|
is_same_image_templ,
|
||||||
case IMAGE_INDEXED: return is_same_image_templ<IndexedTraits>(i1, i2);
|
i1, i2);
|
||||||
case IMAGE_BITMAP: return is_same_image_templ<BitmapTraits>(i1, i2);
|
|
||||||
case IMAGE_TILEMAP: return is_same_image_templ<TilemapTraits>(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);
|
ASSERT(false);
|
||||||
return 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");
|
static_assert(sizeof(void*) == 4, "This CPU is not 32-bit");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const uint32_t rowlen = ImageTraits::getRowStrideBytes(bounds.w);
|
const uint32_t widthBytes = ImageTraits::bytes_per_pixel * bounds.w;
|
||||||
const uint32_t len = rowlen * bounds.h;
|
const uint32_t len = widthBytes * bounds.h;
|
||||||
if (bounds == image->bounds()) {
|
if (bounds == image->bounds() &&
|
||||||
|
widthBytes == image->rowBytes()) {
|
||||||
return CITYHASH((const char*)image->getPixelAddress(0, 0), len);
|
return CITYHASH((const char*)image->getPixelAddress(0, 0), len);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ASSERT(false); // TODO not used at this moment
|
|
||||||
|
|
||||||
std::vector<uint8_t> buf(len);
|
std::vector<uint8_t> buf(len);
|
||||||
uint8_t* dst = &buf[0];
|
uint8_t* dst = &buf[0];
|
||||||
for (int y=0; y<bounds.h; ++y, dst+=rowlen) {
|
for (int y=0; y<bounds.h; ++y, dst+=widthBytes) {
|
||||||
auto src = image->getPixelAddress(bounds.x, bounds.y+y);
|
auto src = (const uint8_t*)image->getPixelAddress(bounds.x, bounds.y+y);
|
||||||
std::copy(dst, dst+rowlen, src);
|
std::copy(src, src+widthBytes, dst);
|
||||||
}
|
}
|
||||||
return CITYHASH((const char*)&buf[0], buf.size());
|
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);
|
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(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);
|
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
|
// Aseprite Document Library
|
||||||
|
// Copyright (c) 2023 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2015 David Capello
|
// Copyright (c) 2001-2015 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -9,6 +10,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "doc/color.h"
|
#include "doc/color.h"
|
||||||
|
#include "doc/image_traits.h"
|
||||||
|
|
||||||
namespace doc {
|
namespace doc {
|
||||||
class Image;
|
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;
|
std::vector<ImageRef> images;
|
||||||
getImages(images);
|
getImages(images);
|
||||||
for (const ImageRef& image : images)
|
for (const ImageRef& image : images)
|
||||||
size += image->getRowStrideSize() * image->height();
|
size += image->rowBytes() * image->height();
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ assert(a.width == 32)
|
|||||||
assert(a.height == 64)
|
assert(a.height == 64)
|
||||||
assert(a.colorMode == ColorMode.RGB) -- RGB by default
|
assert(a.colorMode == ColorMode.RGB) -- RGB by default
|
||||||
assert(a.rowStride == 32*4)
|
assert(a.rowStride == 32*4)
|
||||||
|
assert(a.bytesPerPixel == 4)
|
||||||
assert(a:isEmpty())
|
assert(a:isEmpty())
|
||||||
assert(a:isPlain(rgba(0, 0, 0, 0)))
|
assert(a:isPlain(rgba(0, 0, 0, 0)))
|
||||||
assert(a:isPlain(0))
|
assert(a:isPlain(0))
|
||||||
@ -24,6 +25,7 @@ do
|
|||||||
assert(b.height == 64)
|
assert(b.height == 64)
|
||||||
assert(b.colorMode == ColorMode.INDEXED)
|
assert(b.colorMode == ColorMode.INDEXED)
|
||||||
assert(b.rowStride == 32*1)
|
assert(b.rowStride == 32*1)
|
||||||
|
assert(b.bytesPerPixel == 1)
|
||||||
|
|
||||||
local c = Image{ width=32, height=64, colorMode=ColorMode.INDEXED }
|
local c = Image{ width=32, height=64, colorMode=ColorMode.INDEXED }
|
||||||
assert(c.width == 32)
|
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