diff --git a/src/Image.cc b/src/Image.cc index 13b8cf5..00f641e 100644 --- a/src/Image.cc +++ b/src/Image.cc @@ -559,7 +559,9 @@ Image::Image(const std::string& filename, ssize_t width, ssize_t height, bool has_alpha, uint8_t channel_width, uint64_t max_value) : Image(filename.c_str(), width, height, has_alpha, channel_width, max_value) {} Image::~Image() { - free(this->data.raw); + if (this->data.raw) { + free(this->data.raw); + } } bool Image::operator==(const Image& other) const { @@ -1718,4 +1720,140 @@ void Image::resize_blit(const Image& source, ssize_t x, ssize_t y, ssize_t w, } } +static inline constexpr size_t byte_count_for_bit_count(size_t bit_count) { + return (bit_count + 7) & (~7); +} + +BitmapImage::BitmapImage() : width(0), height(0), row_bytes(0), data(nullptr) {} + +BitmapImage::BitmapImage(size_t w, size_t h) + : width(w), + height(h), + row_bytes(byte_count_for_bit_count(this->height)), + data(new uint8_t[this->get_data_size()]) { + memset(this->data, 0, this->get_data_size()); +} + +BitmapImage::BitmapImage(const BitmapImage& im) + : width(im.width), + height(im.height), + row_bytes(byte_count_for_bit_count(this->height)), + data(new uint8_t[this->get_data_size()]) { + memcpy(this->data, im.data, this->get_data_size()); +} + +const BitmapImage& BitmapImage::operator=(const BitmapImage& im) { + this->width = im.width; + this->height = im.height; + this->row_bytes = im.row_bytes; + size_t num_bytes = this->get_data_size(); + if (this->data) { + delete[] this->data; + } + this->data = new uint8_t[num_bytes]; + memcpy(this->data, im.data, num_bytes); + return *this; +} + +BitmapImage::BitmapImage(BitmapImage&& im) { + this->width = im.width; + this->height = im.height; + this->row_bytes = im.row_bytes; + this->data = im.data; + im.width = 0; + im.height = 0; + im.row_bytes = 0; + im.data = nullptr; +} + +BitmapImage& BitmapImage::operator=(BitmapImage&& im) { + this->width = im.width; + this->height = im.height; + this->row_bytes = im.row_bytes; + if (this->data) { + delete[] this->data; + } + this->data = im.data; + im.width = 0; + im.height = 0; + im.row_bytes = 0; + im.data = nullptr; + return *this; +} + +BitmapImage::BitmapImage(FILE* f, size_t width, size_t height) + : BitmapImage(width, height) { + freadx(f, this->data, this->get_data_size()); +} + +BitmapImage::BitmapImage(const char* filename, size_t width, size_t height) + : BitmapImage(width, height) { + auto f = fopen_unique(filename, "rb"); + freadx(f.get(), this->data, this->get_data_size()); +} + +BitmapImage::BitmapImage(const std::string& filename, size_t width, size_t height) + : BitmapImage(filename.c_str(), width, height) {} + +BitmapImage::~BitmapImage() { + if (this->data) { + delete[] this->data; + } +} + +bool BitmapImage::operator==(const BitmapImage& other) const { + if ((this->width != other.width) || (this->height != other.height) || (this->row_bytes != other.row_bytes)) { + return false; + } + return !memcmp(this->data, other.data, this->get_data_size()); +} + +bool BitmapImage::operator!=(const BitmapImage& other) const { + return !this->operator==(other); +} + +void BitmapImage::clear(bool v) { + memset(this->data, v ? 0xFF : 0x00, this->get_data_size()); +} + +bool BitmapImage::read_pixel(size_t x, size_t y) const { + if (x >= this->width || y >= this->height) { + throw runtime_error("out of bounds"); + } + return !!(this->data[y * byte_count_for_bit_count(this->width) + (x >> 3)] & (0x80 >> (x & 7))); +} + +void BitmapImage::write_pixel(size_t x, size_t y, bool v) { + if (x >= this->width || y >= this->height) { + throw runtime_error("out of bounds"); + } + if (v) { + this->data[y * byte_count_for_bit_count(this->width) + (x >> 3)] |= (0x80 >> (x & 7)); + } else { + this->data[y * byte_count_for_bit_count(this->width) + (x >> 3)] &= (0x7F7F >> (x & 7)); + } +} + +void BitmapImage::invert() { + size_t num_bytes = this->get_data_size(); + for (size_t z = 0; z < num_bytes; z++) { + this->data[z] = ~this->data[z]; + } +} + +Image BitmapImage::to_color(uint32_t false_color, uint32_t true_color, bool add_alpha) const { + Image ret(this->width, this->height, add_alpha); + uint8_t pending_bits = 0; + for (size_t y = 0; y < this->height; y++) { + for (size_t x = 0; x < this->width; x++) { + if (!(x & 7)) { + pending_bits = this->data[(y * this->row_bytes) + (x >> 3)]; + } + ret.write_pixel(x, y, (pending_bits & 0x80) ? true_color : false_color); + pending_bits <<= 1; + } + } + return ret; +} + } // namespace phosg diff --git a/src/Image.hh b/src/Image.hh index c56f17b..e6ff7d5 100644 --- a/src/Image.hh +++ b/src/Image.hh @@ -11,7 +11,7 @@ namespace phosg { -// an Image represents a drawing canvas. this class is fairly simple; it +// An Image represents a drawing canvas. This class is fairly simple; it // supports reading/writing individual pixels, drawing lines, and saving the // image as a PPM or Windows BMP file. class Image { @@ -24,6 +24,8 @@ public: // copy/move from another image Image(const Image&); Image(Image&&); + const Image& operator=(const Image& other); + Image& operator=(Image&& other); // load a file (autodetect format) explicit Image(FILE* f); @@ -42,9 +44,6 @@ public: ~Image(); - const Image& operator=(const Image& other); - Image& operator=(Image&& other); - bool operator==(const Image& other) const; bool operator!=(const Image& other) const; @@ -196,4 +195,56 @@ private: void save_helper(Format format, Writer&& writer) const; }; +// A BitmapImage represents a monochrome (black and white) drawing canvas +class BitmapImage { +public: + BitmapImage(); + BitmapImage(size_t w, size_t h); + + BitmapImage(FILE* f, size_t width, size_t height); + BitmapImage(const char* filename, size_t width, size_t height); + BitmapImage(const std::string& filename, size_t width, size_t height); + + BitmapImage(const BitmapImage&); + BitmapImage(BitmapImage&&); + const BitmapImage& operator=(const BitmapImage& other); + BitmapImage& operator=(BitmapImage&& other); + + ~BitmapImage(); + + bool operator==(const BitmapImage& other) const; + bool operator!=(const BitmapImage& other) const; + + inline size_t get_width() const { + return this->width; + } + inline size_t get_height() const { + return this->height; + } + inline const void* get_data() const { + return this->data; + } + inline size_t get_data_size() const { + return this->height * this->row_bytes; + } + + inline bool empty() const { + return (this->data == nullptr); + } + + void clear(bool v); + bool read_pixel(size_t x, size_t y) const; + void write_pixel(size_t x, size_t y, bool v); + + void invert(); + + Image to_color(uint32_t false_color, uint32_t true_color, bool add_alpha) const; + +private: + size_t width; + size_t height; + size_t row_bytes; + uint8_t* data; +}; + } // namespace phosg diff --git a/src/ImageTest.cc b/src/ImageTest.cc index 225aa9e..6766ee8 100644 --- a/src/ImageTest.cc +++ b/src/ImageTest.cc @@ -44,7 +44,7 @@ int main(int, char**) { Image img(180, 190, has_alpha, channel_width); { - fprintf(stderr, "-- [%hhu-bit/%s] metadata\n", channel_width, has_alpha_tag); + fprintf(stderr, "-- [Image:%hhu-bit/%s] metadata\n", channel_width, has_alpha_tag); expect_eq(180, img.get_width()); expect_eq(190, img.get_height()); expect_eq(has_alpha, img.get_has_alpha()); @@ -52,12 +52,12 @@ int main(int, char**) { } { - fprintf(stderr, "-- [%hhu-bit/%s] clear\n", channel_width, has_alpha_tag); + fprintf(stderr, "-- [Image:%hhu-bit/%s] clear\n", channel_width, has_alpha_tag); img.clear(0x20, 0x20, 0x20, 0x20); } { - fprintf(stderr, "-- [%hhu-bit/%s] axis-aligned lines\n", channel_width, has_alpha_tag); + fprintf(stderr, "-- [Image:%hhu-bit/%s] axis-aligned lines\n", channel_width, has_alpha_tag); for (size_t x = 0; x < 8; x++) { const auto& c = colors[x]; img.draw_horizontal_line(5, 175, 90 + x, x, c.r, c.g, c.b, c.a); @@ -66,12 +66,12 @@ int main(int, char**) { } { - fprintf(stderr, "-- [%hhu-bit/%s] rectangles\n", channel_width, has_alpha_tag); + fprintf(stderr, "-- [Image:%hhu-bit/%s] rectangles\n", channel_width, has_alpha_tag); img.fill_rect(3, 98, 64, 64, 0xFF, 0xFF, 0xFF, 0x80); } { - fprintf(stderr, "-- [%hhu-bit/%s] non-axis-aligned lines\n", channel_width, has_alpha_tag); + fprintf(stderr, "-- [Image:%hhu-bit/%s] non-axis-aligned lines\n", channel_width, has_alpha_tag); const vector> points({ pair(0, 0), pair(0, 20), @@ -100,17 +100,17 @@ int main(int, char**) { } { - fprintf(stderr, "-- [%hhu-bit/%s] blit\n", channel_width, has_alpha_tag); + fprintf(stderr, "-- [Image:%hhu-bit/%s] blit\n", channel_width, has_alpha_tag); img.blit(img, 40, 105, 80, 80, 5, 5); } { - fprintf(stderr, "-- [%hhu-bit/%s] mask_blit with color mask\n", channel_width, has_alpha_tag); + fprintf(stderr, "-- [Image:%hhu-bit/%s] mask_blit with color mask\n", channel_width, has_alpha_tag); img.mask_blit(img, 80, 105, 80, 80, 5, 5, 0x20, 0x20, 0x20); } { - fprintf(stderr, "-- [%hhu-bit/%s] copy\n", channel_width, has_alpha_tag); + fprintf(stderr, "-- [Image:%hhu-bit/%s] copy\n", channel_width, has_alpha_tag); Image img2(img); expect_eq(img, img2); } @@ -134,7 +134,7 @@ int main(int, char**) { } { - fprintf(stderr, "-- [%hhu-bit/%s/%s] save to disk\n", channel_width, has_alpha_tag, ext); + fprintf(stderr, "-- [Image:%hhu-bit/%s/%s] save to disk\n", channel_width, has_alpha_tag, ext); img.save(temp_filename.c_str(), format); if (!reference_data.empty()) { expect_eq(reference_data, load_file(temp_filename)); @@ -142,7 +142,7 @@ int main(int, char**) { } { - fprintf(stderr, "-- [%hhu-bit/%s/%s] save in memory\n", channel_width, has_alpha_tag, ext); + fprintf(stderr, "-- [Image:%hhu-bit/%s/%s] save in memory\n", channel_width, has_alpha_tag, ext); string data = img.save(format); if (!reference_data.empty()) { expect_eq(reference_data, data); @@ -150,13 +150,13 @@ int main(int, char**) { } { - fprintf(stderr, "-- [%hhu-bit/%s/%s] compare with saved image\n", channel_width, has_alpha_tag, ext); + fprintf(stderr, "-- [Image:%hhu-bit/%s/%s] compare with saved image\n", channel_width, has_alpha_tag, ext); Image ref(temp_filename); expect_eq(ref, img); } if (!reference_data.empty()) { - fprintf(stderr, "-- [%hhu-bit/%s/%s] compare with reference\n", channel_width, has_alpha_tag, ext); + fprintf(stderr, "-- [Image:%hhu-bit/%s/%s] compare with reference\n", channel_width, has_alpha_tag, ext); Image ref(reference_filename); expect_eq(ref, img); } @@ -166,6 +166,115 @@ int main(int, char**) { } } + { + BitmapImage bm(202, 206); + + { + fprintf(stderr, "-- [BitmapImage] metadata\n"); + expect_eq(202, bm.get_width()); + expect_eq(206, bm.get_height()); + } + + { + fprintf(stderr, "-- [BitmapImage] clear\n"); + for (size_t y = 0; y < bm.get_height(); y++) { + for (size_t x = 0; x < bm.get_width(); x++) { + expect_eq(false, bm.read_pixel(x, y)); + } + } + bm.clear(true); + for (size_t y = 0; y < bm.get_height(); y++) { + for (size_t x = 0; x < bm.get_width(); x++) { + expect_eq(true, bm.read_pixel(x, y)); + } + } + bm.clear(false); + for (size_t y = 0; y < bm.get_height(); y++) { + for (size_t x = 0; x < bm.get_width(); x++) { + expect_eq(false, bm.read_pixel(x, y)); + } + } + } + + { + fprintf(stderr, "-- [BitmapImage] read/write pixels\n"); + expect_eq(false, bm.read_pixel(0, 0)); + expect_eq(false, bm.read_pixel(1, 1)); + expect_eq(false, bm.read_pixel(101, 101)); + bm.write_pixel(1, 1, true); + expect_eq(false, bm.read_pixel(0, 0)); + expect_eq(true, bm.read_pixel(1, 1)); + expect_eq(false, bm.read_pixel(101, 101)); + bm.write_pixel(101, 101, true); + expect_eq(false, bm.read_pixel(0, 0)); + expect_eq(true, bm.read_pixel(1, 1)); + expect_eq(true, bm.read_pixel(101, 101)); + } + + { + fprintf(stderr, "-- [BitmapImage] write pattern"); + for (size_t y = 0; y < bm.get_height(); y++) { + for (size_t x = 0; x < bm.get_width(); x++) { + bm.write_pixel(x, y, (x & y) & 1); + } + } + for (size_t y = 0; y < bm.get_height(); y++) { + for (size_t x = 0; x < bm.get_width(); x++) { + expect_eq((x & y) & 1, bm.read_pixel(x, y)); + } + } + } + + { + fprintf(stderr, "-- [BitmapImage] copy\n"); + BitmapImage bm2(bm); + expect_eq(bm, bm2); + } + + { + string reference_filename = "reference/BitmapImageTestReference.bmp"; + string temp_filename = "BitmapImageTestImage.bmp"; + + string reference_data; + if (isfile(reference_filename)) { + reference_data = load_file(reference_filename); + } else { + fprintf(stderr, "warning: reference file %s not found; skipping verification\n", reference_filename.c_str()); + } + + Image img; + { + fprintf(stderr, "-- [BitmapImage] convert to Image\n"); + + img = bm.to_color(0x400000E0, 0x00C00080, false); + expect_eq(img.get_has_alpha(), false); + expect_eq(0x400000FF, img.read_pixel(0, 0)); + expect_eq(0x00C000FF, img.read_pixel(1, 1)); + + img = bm.to_color(0x400000E0, 0x00C00080, true); + expect_eq(img.get_has_alpha(), true); + expect_eq(0x400000E0, img.read_pixel(0, 0)); + expect_eq(0x00C00080, img.read_pixel(1, 1)); + } + + { + fprintf(stderr, "-- [BitmapImage] save\n"); + img.save(temp_filename.c_str(), Image::Format::WINDOWS_BITMAP); + if (!reference_data.empty()) { + expect_eq(reference_data, load_file(temp_filename)); + } + } + + if (!reference_data.empty()) { + fprintf(stderr, "-- [BitmapImage] compare with reference\n"); + Image ref(reference_filename); + expect_eq(ref, img); + } + + // unlink(temp_filename); + } + } + printf("ImageTest: all tests passed\n"); return 0; }