Skip to content
Permalink
Browse files

Image Format Extensions & libpng (#1548)

* Remove LodePNG (slow, feature lacking, and unavailable in package managers)
* Add a shared libpng utility library
* Change emake/libEGM/CompilerSource to use libpng utility library
* Make image_formats hookable to support image format extensions
* Provide libpng engine support in the form of an image format extension
  • Loading branch information...
RobertBColton committed Feb 16, 2019
1 parent ff78397 commit 112dc544d611d5b588e8f0b2e3f193b79e7f0845
@@ -18,14 +18,16 @@ before_install:
sudo add-apt-repository -y ppa:maarten-fonville/protobuf;
sudo add-apt-repository -y ppa:mhier/libboost-latest;
sudo apt-get update --option Acquire::Retries=100 --option Acquire::http::Timeout="60";
sudo apt-get -y install build-essential zlib1g-dev libboost1.67-dev \
libprotobuf-dev protobuf-compiler libglm-dev;
# we always build the frontend as 64 bit, even for 32 bit game jobs
# this is why we first install 64 bit png, then 32 bit png below
sudo apt-get -y install build-essential zlib1g-dev libboost1.67-dev\
libprotobuf-dev protobuf-compiler libglm-dev libpng-dev;
if [ "$COMPILER" == "gcc32" ] || [ "$COMPILER" == "clang32" ]; then
sudo dpkg --add-architecture i386;
sudo apt-get -y install libc6:i386 libc++-dev:i386 libstdc++6:i386\
libncurses5:i386 lib32z1-dev libx11-6:i386 libglew-dev:i386\
libglu1-mesa-dev:i386 libgl1-mesa-dev:i386 libxrandr-dev:i386;
libncurses5:i386 libx11-6:i386 libglew-dev:i386 libglu1-mesa-dev:i386\
libgl1-mesa-dev:i386 lib32z1-dev libxrandr-dev:i386;
if [ "$COMPILER" == "gcc32" ]; then
sudo apt-get -y install gcc-multilib g++-multilib;
@@ -85,6 +87,10 @@ install:
if [ "$TEST_HARNESS" != true ]; then
make -j4
CLI_ENABLE_EGM=FALSE make -j4 emake
if [ "$COMPILER" == "gcc32" ] || [ "$COMPILER" == "clang32" ]; then
# frontend uses 64 bit libpng but game will use 32 bit libpng
sudo apt-get -y install libpng-dev:i386;
fi
fi
# by default most of our jobs will be run on a linux instance
@@ -13,9 +13,9 @@ get_filename_component(ENIGMA_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PATH)
get_filename_component(ENIGMA_DIR "${ENIGMA_DIR}" PATH)

if(MSVC)
add_library(${LIB} STATIC ${SRC_FILES} "${ENIGMA_DIR}/shared/lodepng/lodepng.cpp")
add_library(${LIB} STATIC ${SRC_FILES} "${ENIGMA_DIR}/shared/libpng-util/libpng-util.cpp")
else()
add_library(${LIB} SHARED ${SRC_FILES} "${ENIGMA_DIR}/shared/lodepng/lodepng.cpp")
add_library(${LIB} SHARED ${SRC_FILES} "${ENIGMA_DIR}/shared/libpng-util/libpng-util.cpp")
endif(MSVC)

set_target_properties(${LIB} PROPERTIES VERSION ${LIB_VERSION})
@@ -53,7 +53,7 @@ endif(MSVC)
find_package(yaml-cpp CONFIG REQUIRED)
target_link_libraries(${LIB} PRIVATE yaml-cpp)

include_directories(. "${ENIGMA_DIR}/shared/protos/" "${ENIGMA_DIR}/shared/lodepng")
include_directories(. "${ENIGMA_DIR}/shared/protos/" "${ENIGMA_DIR}/shared/libpng-util")

include(FindProtobuf)
target_link_libraries(${LIB} PRIVATE ${Protobuf_LIBRARY})
@@ -14,8 +14,8 @@ PROTO_DIR := ../../shared/protos
SRC_DIR := .
OBJ_DIR := .eobjs

CXXFLAGS := -I$(SRC_DIR) $(shell pkg-config --cflags pugixml) -I$(PROTO_DIR) -I$(PROTO_DIR)/codegen -I$(SHARED_SOURCES)/lodepng -Wall -Wextra -Wpedantic -g -fPIC
LDFLAGS := -lz $(shell pkg-config --libs-only-L pugixml) -lpugixml -lyaml-cpp -L../../ -lProtocols -lprotobuf -lstdc++fs -L$(SHARED_SOURCES)/lodepng -llodepng
CXXFLAGS := -I$(SRC_DIR) $(shell pkg-config --cflags pugixml) -I$(PROTO_DIR) -I$(PROTO_DIR)/codegen -I$(SHARED_SOURCES)/libpng-util -Wall -Wextra -Wpedantic -g -fPIC
LDFLAGS := -lz $(shell pkg-config --libs-only-L pugixml) -lpugixml -lyaml-cpp -L../../ -lProtocols -lprotobuf -lstdc++fs -L$(SHARED_SOURCES)/libpng-util -lpng-util -lpng

#if gcc >= 8 use std::filesystem instead of boost
ifeq ($(shell expr $(GCCVER) \>= 8), 1)
@@ -18,8 +18,7 @@
#include "gmk.h"
#include "filesystem.h"

#include "lodepng.h"
#include <zlib.h>
#include "libpng-util.h"

#include <fstream>
#include <utility>
@@ -33,6 +32,8 @@
#include <cstdlib> /* srand, rand */
#include <ctime> /* time */

#include <zlib.h>

using namespace buffers;
using namespace buffers::resources;
using namespace std;
@@ -112,7 +113,7 @@ std::string writeTempBMPFile(std::unique_ptr<char[]> bytes, size_t length, bool
t_pixel_b = bmp[pixeloffset+2];

/*
There are 3 differences between BMP and the raw image buffer for LodePNG:
There are 3 differences between BMP and the raw image buffer for libpng:
-it's upside down
-it's in BGR instead of RGB format (or BRGA instead of RGBA)
-each scanline has padding bytes to make it a multiple of 4 if needed
@@ -145,40 +146,27 @@ std::string writeTempBMPFile(std::unique_ptr<char[]> bytes, size_t length, bool
}
}

unsigned char *buffer = nullptr;
size_t buffer_length;
lodepng_encode32(&buffer, &buffer_length, rgba.data(), w, h);
std::string temp_file_path = TempFileName("gmk_data");
libpng_encode32_file(rgba.data(), w, h, temp_file_path.c_str());

char *buffer_signed = reinterpret_cast<char*>(buffer);
std::string temp_file_path = writeTempDataFile(buffer_signed, buffer_length);
// explicitly free because lodepng allocated it with malloc
free(buffer);
return temp_file_path;
}

std::string writeTempBGRAFile(std::unique_ptr<char[]> bytes, size_t width, size_t height) {
auto bgra = reinterpret_cast<const unsigned char*>(bytes.get()); // all of the following logic expects unsigned
std::vector<unsigned char> rgba;
rgba.resize(width * height * 4);
auto bgra = reinterpret_cast<unsigned char*>(bytes.get()); // all of the following logic expects unsigned

for (unsigned y = 0; y < height; y++) {
for (unsigned x = 0; x < width; x++) {
unsigned pos = width * 4 * y + 4 * x;
rgba[pos + 0] = bgra[pos + 2]; //R<-B
rgba[pos + 1] = bgra[pos + 1]; //G<-G
rgba[pos + 2] = bgra[pos + 0]; //B<-R
rgba[pos + 3] = bgra[pos + 3]; //A<-A
unsigned char temp = bgra[pos + 2];
bgra[pos + 2] = bgra[pos + 0];
bgra[pos + 0] = temp;
}
}

unsigned char *buffer = nullptr;
size_t buffer_length;
lodepng_encode32(&buffer, &buffer_length, rgba.data(), width, height);
std::string temp_file_path = TempFileName("gmk_data");
libpng_encode32_file(bgra, width, height, temp_file_path.c_str());

char *buffer_signed = reinterpret_cast<char*>(buffer);
std::string temp_file_path = writeTempDataFile(buffer_signed, buffer_length);
// explicitly free because lodepng allocated it with malloc
free(buffer);
return temp_file_path;
}

@@ -102,7 +102,7 @@ class SimpleTestHarness : public testing::TestWithParam<string> {};
TEST_P(SimpleTestHarness, SimpleTestRunner) {
string game = GetParam();
TestConfig tc;
tc.extensions = "Paths,GTest";
tc.extensions = "Paths,GTest,libpng";
int ret = TestHarness::run_to_completion(game, tc);
if (!ret) return;
switch (ret) {
@@ -4,7 +4,9 @@

TEST(Regression, draw_test) {
if (!TestHarness::windowing_supported()) return;
auto test_harness = LAUNCH_HARNESS_FOR_SOG(TestConfig());
TestConfig tc;
tc.extensions = "Paths,libpng";
auto test_harness = LAUNCH_HARNESS_FOR_SOG(tc);
if (!test_harness) FAIL() << "Game could not be run.";

test_harness->wait(); // Let the game render a frame first.
@@ -4,7 +4,9 @@

TEST(Regression, draw_tiles_test) {
if (!TestHarness::windowing_supported()) return;
auto test_harness = LAUNCH_HARNESS_FOR_SOG(TestConfig());
TestConfig tc;
tc.extensions = "Paths,libpng";
auto test_harness = LAUNCH_HARNESS_FOR_SOG(tc);
if (!test_harness) FAIL() << "Game could not be run.";

test_harness->wait(); // Let the game render a frame first.
@@ -32,7 +32,7 @@ endif
CXX := g++
CXXFLAGS += -std=c++11 -Wall -O3 -g -I./JDI/src
LDFLAGS += -shared -O3 -g -L../ -Wl,-rpath=./
LDLIBS += -lProtocols -lprotobuf -lz -L$(SHARED_SRC_DIR)/lodepng -llodepng
LDLIBS += -lProtocols -lprotobuf -lz -L$(SHARED_SRC_DIR)/libpng-util -lpng-util -lpng

# This implements a recursive wildcard allowing us to iterate in subdirs
rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
@@ -42,7 +42,7 @@ OBJECTS := $(addprefix .eobjs/,$(SOURCES:.cpp=.o))
DEPENDS := $(OBJECTS:.o=.d)
PROTO_DIR := $(SHARED_SRC_DIR)/protos

CXXFLAGS += -I$(SHARED_SRC_DIR) -I$(SHARED_SRC_DIR)/lodepng -I$(PROTO_DIR)/codegen $(addprefix -I$(SHARED_SRC_DIR)/, $(SHARED_INCLUDES))
CXXFLAGS += -I$(SHARED_SRC_DIR) -I$(SHARED_SRC_DIR)/libpng-util -I$(PROTO_DIR)/codegen $(addprefix -I$(SHARED_SRC_DIR)/, $(SHARED_INCLUDES))
SOURCES += $(addprefix $(SHARED_SRC_DIR),$(SHARED_SOURCES))
OBJECTS += $(addprefix .eobjs/shared/,$(SHARED_SOURCES:.cpp=.o))
DEPENDS += $(addprefix .eobjs/shared/,$(SHARED_SOURCES:.cpp=.d))
@@ -9,7 +9,7 @@
#include "GameData.h"
#include "Actions2Code.h"

#include "lodepng.h"
#include "libpng-util.h"

#include <map>
#include <string>
@@ -64,32 +64,27 @@ ImageData loadImageData(const std::string &filePath, int &errorc) {
unsigned char* image;
unsigned pngwidth, pngheight;

error = lodepng_decode32_file(&image, &pngwidth, &pngheight, filePath.c_str());
error = libpng_decode32_file(&image, &pngwidth, &pngheight, filePath.c_str());
if (error) {
errorc = -1;
printf("error %u: %s\n", error, lodepng_error_text(error));
printf("libpng-util error %u\n", error);
return ImageData(0, 0, 0, 0);
}

unsigned ih,iw;
const int bitmap_size = pngwidth*pngheight*4;
unsigned char* bitmap = new unsigned char[bitmap_size](); // Initialize to zero.

for (ih = 0; ih < pngheight; ih++) {
unsigned tmp = ih*pngwidth*4;
for (iw = 0; iw < pngwidth; iw++) {
bitmap[tmp+0] = image[4*pngwidth*ih+iw*4+2];
bitmap[tmp+1] = image[4*pngwidth*ih+iw*4+1];
bitmap[tmp+2] = image[4*pngwidth*ih+iw*4+0];
bitmap[tmp+3] = image[4*pngwidth*ih+iw*4+3];
tmp+=4;
const unsigned pitch = pngwidth * 4;
for (unsigned i = 0; i < pngheight; i++) {
const unsigned row = i*pitch;
for (unsigned j = 0; j < pngwidth; j++) {
const unsigned px = row+j*4;
unsigned char temp = image[px+2];
image[px+2] = image[px+0];
image[px+0] = temp;
}
}

free(image);

int dataSize = bitmap_size;
const unsigned char* data = zlib_compress(bitmap, dataSize);
int dataSize = pngwidth*pngheight*4;
const unsigned char* data = zlib_compress(image, dataSize);
delete[] image;

errorc = 0;
return ImageData(pngwidth, pngheight, data, dataSize);;
@@ -96,10 +96,7 @@ endif

# CPPFLAGS needs these include dirs unconditionally
override CPPFLAGS += $(SYSTEMS:%=-I%/Info)
override CPPFLAGS += -I. -I$(CODEGEN) -I$(SHARED_SRC_DIR)/lodepng -I$(SHARED_SRC_DIR)

# Unconditional LDLIBS
override LDLIBS += -L$(SHARED_SRC_DIR)/lodepng -llodepng
override CPPFLAGS += -I. -I$(CODEGEN) -I$(SHARED_SRC_DIR)

.PHONY: all clean

@@ -0,0 +1,12 @@
%e-yaml
---

Name: libpng
Identifier: libpng
Author: Robert
Description: Support for loading and saving png images using libpng.
Icon: libpng.png

Depends: None
Dependencies: None
Init: extension_libpng_init
@@ -0,0 +1,5 @@
SHARED_SRC_DIR := ../../shared
override CXXFLAGS += -I$(SHARED_SRC_DIR)/libpng-util
override LDLIBS += -L$(SHARED_SRC_DIR)/libpng-util -lpng-util -lpng -lz

SOURCES += Universal_System/Extensions/libpng/libpng-ext.cpp
@@ -0,0 +1,18 @@
/** Copyright (C) 2019 Robert B. Colton
***
*** This file is a part of the ENIGMA Development Environment.
***
*** ENIGMA is free software: you can redistribute it and/or modify it under the
*** terms of the GNU General Public License as published by the Free Software
*** Foundation, version 3 of the license or any later version.
***
*** This application and its source code is distributed AS-IS, WITHOUT ANY
*** WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
*** FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
*** details.
***
*** You should have received a copy of the GNU General Public License along
*** with this code. If not, see <http://www.gnu.org/licenses/>
**/

#include "libpng-ext.h"
Oops, something went wrong.

0 comments on commit 112dc54

Please sign in to comment.
You can’t perform that action at this time.