Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/Image formats.md
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/imageformats/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
option(LIBSCRATCHCPP_JPEG_SUPPORT "JPEG image support" ON)
option(LIBSCRATCHCPP_PNG_SUPPORT "PNG image support" ON)

add_subdirectory(stub)

Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/imageformats/defaultimageformats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
#include "jpeg/jpegimageformatfactory.h"
#endif

#ifdef PNG_SUPPORT
#include "png/pngimageformatfactory.h"
#endif

namespace libscratchcpp
{

Expand All @@ -18,6 +22,10 @@ class DefaultImageFormats
#ifdef JPEG_SUPPORT
ScratchConfiguration::registerImageFormat("jpg", std::make_shared<JpegImageFormatFactory>());
#endif

#ifdef PNG_SUPPORT
ScratchConfiguration::registerImageFormat("png", std::make_shared<PngImageFormatFactory>());
#endif
}
};

Expand Down
9 changes: 9 additions & 0 deletions src/imageformats/png/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
target_sources(scratchcpp
PRIVATE
pngimageformat.cpp
pngimageformat.h
pngimageformatfactory.cpp
pngimageformatfactory.h
)

target_link_libraries(scratchcpp PRIVATE gd)
49 changes: 49 additions & 0 deletions src/imageformats/png/pngimageformat.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
28 changes: 28 additions & 0 deletions src/imageformats/png/pngimageformat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <scratchcpp/iimageformat.h>
#include <gd.h>

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
15 changes: 15 additions & 0 deletions src/imageformats/png/pngimageformatfactory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0

#include "pngimageformatfactory.h"
#include "pngimageformat.h"

using namespace libscratchcpp;

PngImageFormatFactory::PngImageFormatFactory()
{
}

std::shared_ptr<IImageFormat> PngImageFormatFactory::createInstance() const
{
return std::make_shared<PngImageFormat>();
}
18 changes: 18 additions & 0 deletions src/imageformats/png/pngimageformatfactory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <scratchcpp/iimageformatfactory.h>

namespace libscratchcpp
{

class PngImageFormatFactory : public IImageFormatFactory
{
public:
PngImageFormatFactory();

std::shared_ptr<IImageFormat> createInstance() const override;
};

} // namespace libscratchcpp
Binary file added test/image1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions test/imageformats/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
107 changes: 107 additions & 0 deletions test/imageformats/png_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include <scratchcpp/scratchconfiguration.h>
#include <scratchcpp/iimageformat.h>

#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<IImageFormat> img1;
std::shared_ptr<IImageFormat> 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));
}