From 90ccbf4106edc676f8de9e35a291c30bf047ed3c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 28 Oct 2023 11:53:04 +0200 Subject: [PATCH 1/2] Implement PNG image format --- src/imageformats/CMakeLists.txt | 6 + src/imageformats/defaultimageformats.cpp | 8 ++ src/imageformats/png/CMakeLists.txt | 9 ++ src/imageformats/png/pngimageformat.cpp | 49 ++++++++ src/imageformats/png/pngimageformat.h | 28 +++++ .../png/pngimageformatfactory.cpp | 15 +++ src/imageformats/png/pngimageformatfactory.h | 18 +++ test/image1.png | Bin 0 -> 122 bytes test/image2.png | Bin 0 -> 501 bytes test/imageformats/CMakeLists.txt | 14 +++ test/imageformats/png_test.cpp | 107 ++++++++++++++++++ 11 files changed, 254 insertions(+) create mode 100644 src/imageformats/png/CMakeLists.txt create mode 100644 src/imageformats/png/pngimageformat.cpp create mode 100644 src/imageformats/png/pngimageformat.h create mode 100644 src/imageformats/png/pngimageformatfactory.cpp create mode 100644 src/imageformats/png/pngimageformatfactory.h create mode 100644 test/image1.png create mode 100644 test/image2.png create mode 100644 test/imageformats/png_test.cpp diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index 2aee075d..878bb0c4 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -1,4 +1,5 @@ option(LIBSCRATCHCPP_JPEG_SUPPORT "JPEG image support" ON) +option(LIBSCRATCHCPP_PNG_SUPPORT "PNG image support" ON) add_subdirectory(stub) @@ -7,6 +8,11 @@ if (LIBSCRATCHCPP_JPEG_SUPPORT) add_subdirectory(jpeg) endif() +if (LIBSCRATCHCPP_PNG_SUPPORT) + target_compile_definitions(scratchcpp PRIVATE PNG_SUPPORT) + add_subdirectory(png) +endif() + target_sources(scratchcpp PRIVATE defaultimageformats.cpp diff --git a/src/imageformats/defaultimageformats.cpp b/src/imageformats/defaultimageformats.cpp index 775aae2a..94fdbfe6 100644 --- a/src/imageformats/defaultimageformats.cpp +++ b/src/imageformats/defaultimageformats.cpp @@ -7,6 +7,10 @@ #include "jpeg/jpegimageformatfactory.h" #endif +#ifdef PNG_SUPPORT +#include "png/pngimageformatfactory.h" +#endif + namespace libscratchcpp { @@ -18,6 +22,10 @@ class DefaultImageFormats #ifdef JPEG_SUPPORT ScratchConfiguration::registerImageFormat("jpg", std::make_shared()); #endif + +#ifdef PNG_SUPPORT + ScratchConfiguration::registerImageFormat("png", std::make_shared()); +#endif } }; diff --git a/src/imageformats/png/CMakeLists.txt b/src/imageformats/png/CMakeLists.txt new file mode 100644 index 00000000..da9b48ec --- /dev/null +++ b/src/imageformats/png/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources(scratchcpp + PRIVATE + pngimageformat.cpp + pngimageformat.h + pngimageformatfactory.cpp + pngimageformatfactory.h +) + +target_link_libraries(scratchcpp PRIVATE gd) diff --git a/src/imageformats/png/pngimageformat.cpp b/src/imageformats/png/pngimageformat.cpp new file mode 100644 index 00000000..63a86966 --- /dev/null +++ b/src/imageformats/png/pngimageformat.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "pngimageformat.h" + +using namespace libscratchcpp; + +PngImageFormat::PngImageFormat() +{ +} + +PngImageFormat::~PngImageFormat() +{ + if (m_img) + gdImageDestroy(m_img); +} + +void PngImageFormat::setData(unsigned int size, void *data) +{ + if (m_img) + gdImageDestroy(m_img); + + m_img = gdImageCreateFromPngPtr(size, data); +} + +unsigned int PngImageFormat::width() const +{ + return m_img ? gdImageSX(m_img) : 0; +} + +unsigned int PngImageFormat::height() const +{ + return m_img ? gdImageSY(m_img) : 0; +} + +Rgb PngImageFormat::colorAt(unsigned int x, unsigned int y, double scale) const +{ + if (!m_img) + return 0; + + int color = gdImageGetPixel(m_img, x / scale, y / scale); + int alpha = 127 - gdImageAlpha(m_img, color); // gdImageAlpha() returns values from 0 to 127 + + if (alpha == 127) // 127 should be the max (255) + alpha = 255; + else + alpha *= 2; + + return rgba(gdImageRed(m_img, color), gdImageGreen(m_img, color), gdImageBlue(m_img, color), alpha); +} diff --git a/src/imageformats/png/pngimageformat.h b/src/imageformats/png/pngimageformat.h new file mode 100644 index 00000000..7abad264 --- /dev/null +++ b/src/imageformats/png/pngimageformat.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace libscratchcpp +{ + +class PngImageFormat : public IImageFormat +{ + public: + PngImageFormat(); + ~PngImageFormat(); + + void setData(unsigned int size, void *data) override; + + unsigned int width() const override; + unsigned int height() const override; + + Rgb colorAt(unsigned int x, unsigned int y, double scale) const override; + + private: + gdImagePtr m_img = nullptr; +}; + +} // namespace libscratchcpp diff --git a/src/imageformats/png/pngimageformatfactory.cpp b/src/imageformats/png/pngimageformatfactory.cpp new file mode 100644 index 00000000..279ad355 --- /dev/null +++ b/src/imageformats/png/pngimageformatfactory.cpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "pngimageformatfactory.h" +#include "pngimageformat.h" + +using namespace libscratchcpp; + +PngImageFormatFactory::PngImageFormatFactory() +{ +} + +std::shared_ptr PngImageFormatFactory::createInstance() const +{ + return std::make_shared(); +} diff --git a/src/imageformats/png/pngimageformatfactory.h b/src/imageformats/png/pngimageformatfactory.h new file mode 100644 index 00000000..d8aef203 --- /dev/null +++ b/src/imageformats/png/pngimageformatfactory.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class PngImageFormatFactory : public IImageFormatFactory +{ + public: + PngImageFormatFactory(); + + std::shared_ptr createInstance() const override; +}; + +} // namespace libscratchcpp diff --git a/test/image1.png b/test/image1.png new file mode 100644 index 0000000000000000000000000000000000000000..9f5477d0caf57aad360fc09e1efc1fd94e04031d GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJ+0U|dr*8K)joCO|{#S9F5he4R}c>anMprDSY zi(?4KOx+`nyax;zj%?_;xj=S{(Pz22O3RD;az(iBK9V;x{ty!OHEwlQZ7GL}n!e7C QBA`A7Pgg&ebxsLQ00rG6GXMYp literal 0 HcmV?d00001 diff --git a/test/image2.png b/test/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..6be36ef82fc3ecb5549d385ec3e33c285ca5b730 GIT binary patch literal 501 zcmVkYFp=SO^xP zjjzIgpsir5AP9;eVrTEGk+9xLgov0~X79(DIhUC;;E&~X%WWP8t59?j(@||9xu~_i z&_RfH!bDQKYfr}GF&d7~JAA6%MpUoH`~DrB8N=1V*Csrs+fGqn>2U($chqizx~3iGp4 z`(%c2Nia^)EE`OUcjySU9#2Xg1BwY=urs#%zdCDsH zzj?am=t!-5APOx{p7%BYtvgWJ_Pnnn&#N3m(<59I>i$*9Y5YUnU_4q5B3pmTFo3taEY?bL;X%*RZz_gR(!k{0%luXd~7) z>iqx!010qNS#tmY4#WTe4#WYKD-Ig~001pXL_t&t9b;fX1B?*<|Ns9%jD|*-bOV?_ r_5akVEMN&R@&EtpMyLjuI;=DR%Z?1JD4iw&00000NkvXXu0mjfcA?ta literal 0 HcmV?d00001 diff --git a/test/imageformats/CMakeLists.txt b/test/imageformats/CMakeLists.txt index 12e6487c..b92341ea 100644 --- a/test/imageformats/CMakeLists.txt +++ b/test/imageformats/CMakeLists.txt @@ -25,3 +25,17 @@ target_link_libraries( ) gtest_discover_tests(jpeg_test) + +# png +add_executable( + png_test + png_test.cpp +) + +target_link_libraries( + png_test + GTest::gtest_main + scratchcpp +) + +gtest_discover_tests(png_test) diff --git a/test/imageformats/png_test.cpp b/test/imageformats/png_test.cpp new file mode 100644 index 00000000..e73fb38b --- /dev/null +++ b/test/imageformats/png_test.cpp @@ -0,0 +1,107 @@ +#include +#include + +#include "../common.h" + +using namespace libscratchcpp; + +class PngTest : public testing::Test +{ + public: + void SetUp() override + { + img1 = ScratchConfiguration::createImageFormat("png"); + img2 = ScratchConfiguration::createImageFormat("png"); + + struct stat stat_buf; + + FILE *f1 = fopen("image1.png", "r"); + ASSERT_TRUE(f1); + ASSERT_EQ(fstat(fileno(f1), &stat_buf), 0); + file1 = malloc(stat_buf.st_size); + ASSERT_TRUE(file1); + ASSERT_EQ(fread(file1, 1, stat_buf.st_size, f1), stat_buf.st_size); + img1->setData(stat_buf.st_size, file1); + + FILE *f2 = fopen("image2.png", "r"); + ASSERT_TRUE(f2); + ASSERT_EQ(fstat(fileno(f2), &stat_buf), 0); + file2 = malloc(stat_buf.st_size); + ASSERT_TRUE(file2); + ASSERT_EQ(fread(file2, 1, stat_buf.st_size, f2), stat_buf.st_size); + img2->setData(stat_buf.st_size, file2); + } + + void TearDown() override + { + if (file1) + free(file1); + + if (file2) + free(file2); + } + + std::shared_ptr img1; + std::shared_ptr img2; + void *file1 = nullptr; + void *file2 = nullptr; +}; + +TEST_F(PngTest, Width) +{ + ASSERT_EQ(img1->width(), 6); + ASSERT_EQ(img2->width(), 4); +} + +TEST_F(PngTest, Height) +{ + ASSERT_EQ(img1->height(), 3); + ASSERT_EQ(img2->height(), 6); +} + +TEST_F(PngTest, ColorAt) +{ + // image1 - without scaling + ASSERT_EQ(img1->colorAt(0, 0, 1), rgb(192, 192, 192)); + ASSERT_EQ(img1->colorAt(3, 0, 1), rgb(255, 128, 0)); + ASSERT_EQ(img1->colorAt(5, 0, 1), rgb(0, 255, 0)); + + ASSERT_EQ(img1->colorAt(0, 1, 1), rgb(0, 255, 255)); + ASSERT_EQ(img1->colorAt(3, 1, 1), rgb(255, 128, 128)); + ASSERT_EQ(img1->colorAt(5, 1, 1), rgb(255, 255, 255)); + + ASSERT_EQ(img1->colorAt(0, 2, 1), rgb(0, 0, 0)); + ASSERT_EQ(img1->colorAt(3, 2, 1), rgb(128, 128, 0)); + ASSERT_EQ(img1->colorAt(5, 2, 1), rgb(0, 128, 128)); + + // image1 - with scaling + ASSERT_EQ(img1->colorAt(0, 0, 2.5), rgb(192, 192, 192)); + ASSERT_EQ(img1->colorAt(8, 0, 2.5), rgb(255, 128, 0)); + ASSERT_EQ(img1->colorAt(13, 0, 2.5), rgb(0, 255, 0)); + + ASSERT_EQ(img1->colorAt(0, 3, 2.5), rgb(0, 255, 255)); + ASSERT_EQ(img1->colorAt(8, 3, 2.5), rgb(255, 128, 128)); + ASSERT_EQ(img1->colorAt(13, 3, 2.5), rgb(255, 255, 255)); + + ASSERT_EQ(img1->colorAt(0, 5, 2.5), rgb(0, 0, 0)); + ASSERT_EQ(img1->colorAt(8, 5, 2.5), rgb(128, 128, 0)); + ASSERT_EQ(img1->colorAt(13, 5, 2.5), rgb(0, 128, 128)); + + // image2 - without scaling + ASSERT_EQ(img2->colorAt(1, 0, 1), rgba(0, 0, 0, 0)); + ASSERT_EQ(img2->colorAt(3, 1, 1), rgb(255, 128, 128)); + ASSERT_EQ(img2->colorAt(3, 2, 1), rgba(149, 255, 149, 148)); + ASSERT_EQ(img2->colorAt(2, 2, 1), rgba(0, 0, 0, 0)); + ASSERT_EQ(img2->colorAt(0, 3, 1), rgba(0, 0, 0, 0)); + ASSERT_EQ(img2->colorAt(2, 4, 1), rgba(0, 0, 0, 0)); + ASSERT_EQ(img2->colorAt(1, 5, 1), rgba(0, 0, 0, 0)); + + // image2 - with scaling + ASSERT_EQ(img2->colorAt(3, 0, 2.5), rgba(0, 0, 0, 0)); + ASSERT_EQ(img2->colorAt(8, 3, 2.5), rgb(255, 128, 128)); + ASSERT_EQ(img2->colorAt(8, 5, 2.5), rgba(149, 255, 149, 148)); + ASSERT_EQ(img2->colorAt(5, 5, 2.5), rgba(0, 0, 0, 0)); + ASSERT_EQ(img2->colorAt(0, 8, 2.5), rgba(0, 0, 0, 0)); + ASSERT_EQ(img2->colorAt(5, 10, 2.5), rgba(0, 0, 0, 0)); + ASSERT_EQ(img2->colorAt(3, 13, 2.5), rgba(0, 0, 0, 0)); +} From 313d50e4f46b397ad4dc22f15365fead4d45e0e2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sat, 28 Oct 2023 11:54:14 +0200 Subject: [PATCH 2/2] Mention PNG in docs --- docs/Image formats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Image formats.md b/docs/Image formats.md index 46e324b8..f1173bd1 100644 --- a/docs/Image formats.md +++ b/docs/Image formats.md @@ -1,7 +1,7 @@ \page imageFormats Image formats To work with costumes, libscratchcpp has to read image files from Scratch projects. -Only JPEG is currently supported, but support for PNG is coming soon. +Only JPEG and PNG are currently supported, but SVG is going to be supported in the future. # Implementing a custom image format To implement a custom image format that libscratchcpp doesn't support,