Add new kind of Image iterators line by line: Image::read/writeArea()

This commit is contained in:
David Capello 2024-12-23 15:55:43 -03:00
parent 70c8924719
commit 8d4c4857ee
6 changed files with 221 additions and 3 deletions

View File

@ -45,6 +45,7 @@ add_library(doc-lib
image.cpp
image_impl.cpp
image_io.cpp
image_iterators2.cpp
layer.cpp
layer_io.cpp
layer_list.cpp

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2018-2020 Igara Studio S.A.
// Copyright (c) 2018-2024 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2018-2023 Igara Studio S.A.
// Copyright (c) 2018-2024 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -12,6 +12,7 @@
#include "doc/color.h"
#include "doc/color_mode.h"
#include "doc/image_buffer.h"
#include "doc/image_iterators2.h"
#include "doc/image_spec.h"
#include "doc/object.h"
#include "doc/pixel_format.h"
@ -103,6 +104,21 @@ public:
virtual void fillRect(int x1, int y1, int x2, int y2, color_t color) = 0;
virtual void blendRect(int x1, int y1, int x2, int y2, color_t color, int opacity) = 0;
ReadIterator readArea() const { return ReadIterator(this, this->bounds()); }
WriteIterator writeArea() { return WriteIterator(this, this->bounds()); }
ReadIterator readArea(const gfx::Rect& bounds,
const IteratorStart start = IteratorStart::TopLeft) const
{
return ReadIterator(this, bounds, start);
}
WriteIterator writeArea(const gfx::Rect& bounds,
const IteratorStart start = IteratorStart::TopLeft)
{
return WriteIterator(this, bounds, start);
}
protected:
Image(const ImageSpec& spec);

View File

@ -0,0 +1,39 @@
// Aseprite Document Library
// Copyright (c) 2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/image.h"
namespace doc {
ReadIterator::ReadIterator(const Image* image, const gfx::Rect& bounds, const IteratorStart start)
: m_rows(bounds.h)
{
switch (start) {
case IteratorStart::TopLeft:
m_addr = image->getPixelAddress(bounds.x, bounds.y);
m_nextRow = image->rowBytes();
break;
case IteratorStart::TopRight:
m_addr = image->getPixelAddress(bounds.x2() - 1, bounds.y);
m_nextRow = image->rowBytes();
break;
case IteratorStart::BottomLeft:
m_addr = image->getPixelAddress(bounds.x, bounds.y2() - 1);
m_nextRow = -image->rowBytes();
break;
case IteratorStart::BottomRight:
m_addr = image->getPixelAddress(bounds.x2() - 1, bounds.y2() - 1);
m_nextRow = -image->rowBytes();
break;
}
m_addr -= m_nextRow; // This is canceled in the first nextLine() call.
}
} // namespace doc

View File

@ -0,0 +1,74 @@
// Aseprite Document Library
// Copyright (c) 2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_IMAGE_ITERATOR2_H_INCLUDED
#define DOC_IMAGE_ITERATOR2_H_INCLUDED
#pragma once
#include "base/ints.h"
#include "gfx/fwd.h"
namespace doc {
class Image;
// New iterators classes.
enum class IteratorStart : uint8_t { TopLeft, TopRight, BottomLeft, BottomRight };
class ReadIterator {
public:
ReadIterator(const Image* image,
const gfx::Rect& bounds,
IteratorStart start = IteratorStart::TopLeft);
const uint8_t* addr8() const { return m_addr; }
const uint16_t* addr16() const { return (uint16_t*)m_addr; }
const uint32_t* addr32() const { return (uint32_t*)m_addr; }
template<typename ImageTraits>
typename ImageTraits::const_address_t addr() const
{
return (typename ImageTraits::const_address_t)m_addr;
}
bool nextLine()
{
m_addr += m_nextRow;
return (m_rows-- > 0);
}
protected:
uint8_t* m_addr = nullptr;
private:
int m_rows = 0;
int m_nextRow = 0;
};
class WriteIterator : public ReadIterator {
public:
WriteIterator(Image* image,
const gfx::Rect& bounds,
const IteratorStart start = IteratorStart::TopLeft)
: ReadIterator(image, bounds, start)
{
}
uint8_t* addr8() { return m_addr; }
uint16_t* addr16() { return (uint16_t*)m_addr; }
uint32_t* addr32() { return (uint32_t*)m_addr; }
template<typename ImageTraits>
uint32_t* addr()
{
return (typename ImageTraits::address_t)m_addr;
}
};
} // namespace doc
#endif

View File

@ -11,6 +11,7 @@
#include <gtest/gtest.h>
#include "doc/algorithm/random_image.h"
#include "doc/image.h"
#include "doc/primitives.h"
@ -25,9 +26,27 @@ protected:
ImageAllTypes() {}
};
typedef testing::Types<RgbTraits, GrayscaleTraits, IndexedTraits, BitmapTraits> ImageAllTraits;
using ImageAllTraits = testing::Types<RgbTraits, GrayscaleTraits, IndexedTraits, BitmapTraits>;
TYPED_TEST_SUITE(ImageAllTypes, ImageAllTraits);
#if DOC_USE_BITMAP_AS_1BPP
template<typename T>
class ImageAllTypesNoBitmap : public testing::Test {
protected:
ImageAllTypesNoBitmap() {}
};
using ImageAllTraitsNoBitmap = testing::Types<RgbTraits, GrayscaleTraits, IndexedTraits>;
TYPED_TEST_SUITE(ImageAllTypesNoBitmap, ImageAllTraitsNoBitmap);
#else // !DOC_USE_BITMAP_AS_1BPP
#define ImageAllTypesNoBitmap ImageAllTypes
#endif // !DOC_USE_BITMAP_AS_1BPP
TYPED_TEST(ImageAllTypes, PutGetAndIterators)
{
using ImageTraits = TypeParam;
@ -195,6 +214,75 @@ TYPED_TEST(ImageAllTypes, FillRect)
}
}
TYPED_TEST(ImageAllTypesNoBitmap, NewIterators)
{
using ImageTraits = TypeParam;
for (int i = 0; i < 100; ++i) {
const int w = 1 + i;
const int h = 1 + i;
std::unique_ptr<Image> image(Image::create(ImageTraits::pixel_format, w, h));
doc::algorithm::random_image(image.get());
// TopLeft
{
int v = 0;
auto it = image->readArea(image->bounds(), IteratorStart::TopLeft);
while (it.nextLine()) {
auto* addr = (typename ImageTraits::address_t)it.addr8();
for (int u = 0; u < w; ++u, ++addr) {
auto expected = get_pixel_fast<ImageTraits>(image.get(), u, v);
ASSERT_EQ(expected, *addr);
}
++v;
}
}
// TopRight
{
int v = 0;
auto it = image->readArea(image->bounds(), IteratorStart::TopRight);
while (it.nextLine()) {
auto* addr = (typename ImageTraits::address_t)it.addr8();
for (int u = w - 1; u >= 0; --u, --addr) {
auto expected = get_pixel_fast<ImageTraits>(image.get(), u, v);
ASSERT_EQ(expected, *addr);
}
++v;
}
}
// BottomLeft
{
int v = h - 1;
auto it = image->readArea(image->bounds(), IteratorStart::BottomLeft);
while (it.nextLine()) {
auto* addr = (typename ImageTraits::address_t)it.addr8();
for (int u = 0; u < w; ++u, ++addr) {
auto expected = get_pixel_fast<ImageTraits>(image.get(), u, v);
ASSERT_EQ(expected, *addr);
}
--v;
}
}
// BottomRight
{
int v = h - 1;
auto it = image->readArea(image->bounds(), IteratorStart::BottomRight);
while (it.nextLine()) {
auto* addr = (typename ImageTraits::address_t)it.addr8();
for (int u = w - 1; u >= 0; --u, --addr) {
auto expected = get_pixel_fast<ImageTraits>(image.get(), u, v);
ASSERT_EQ(expected, *addr);
}
--v;
}
}
}
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);