mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
[ion/simulator] Support writing image files per-platform
Linux uses libpng, macOS/iOS use CoreGraphics, Windows GDI+
This commit is contained in:
@@ -23,7 +23,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \
|
||||
display.cpp:-headless \
|
||||
events.cpp \
|
||||
events_platform.cpp:-headless \
|
||||
framebuffer_base.cpp \
|
||||
framebuffer.cpp \
|
||||
framebuffer_png.cpp:+headless \
|
||||
keyboard.cpp:-headless \
|
||||
layout.cpp:-headless \
|
||||
@@ -39,3 +39,11 @@ ion_simulator_assets_paths = $(add_prefix ion/src/simulator/assets/,$(ion_simula
|
||||
|
||||
include ion/src/simulator/$(TARGET)/Makefile
|
||||
include ion/src/simulator/external/Makefile
|
||||
|
||||
ifeq ($(ION_SIMULATOR_FILES),1)
|
||||
ion_src += $(addprefix ion/src/simulator/shared/, \
|
||||
actions.cpp \
|
||||
state_file.cpp \
|
||||
)
|
||||
SFLAGS += -DION_SIMULATOR_FILES=1
|
||||
endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# The following lines allow us to use our own SDL_config.h
|
||||
ION_SIMULATOR_FILES = 1
|
||||
|
||||
# The following lines allow us to use our own SDL_config.h
|
||||
# First, make sure an error is raised if we ever use the standard SDL_config.h
|
||||
SFLAGS += -DUSING_GENERATED_CONFIG_H
|
||||
# Then use our very own include dir if either SDL.h or SDL_config.h are included
|
||||
@@ -33,7 +34,7 @@ ion_src += ion/src/simulator/shared/dummy/telemetry_init.cpp
|
||||
ion_src += ion/src/shared/telemetry_console.cpp
|
||||
endif
|
||||
|
||||
LDFLAGS += -ljpeg
|
||||
LDFLAGS += -ljpeg -lpng
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
INCBIN, \
|
||||
@@ -46,4 +47,4 @@ $(eval $(call rule_for, \
|
||||
$(call object_for,ion/src/simulator/linux/platform_images.cpp): $(BUILD_DIR)/ion/src/simulator/linux/platform_images.h
|
||||
|
||||
# The header is refered to as <ion/src/simulator/linux/platform_images.h> so make sure it's findable this way
|
||||
SFLAGS += -I$(BUILD_DIR)
|
||||
$(call object_for,ion/src/simulator/linux/platform_images.cpp): SFLAGS += -I$(BUILD_DIR)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include "../shared/platform.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <assert.h>
|
||||
#include <jpeglib.h>
|
||||
#include <png.h>
|
||||
#include <assert.h>
|
||||
#include <SDL.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <ion/src/simulator/linux/platform_images.h>
|
||||
|
||||
@@ -166,6 +167,54 @@ SDL_Texture * loadImage(SDL_Renderer * renderer, const char * identifier) {
|
||||
return texture;
|
||||
}
|
||||
|
||||
class RGB888Pixel {
|
||||
public:
|
||||
RGB888Pixel() {}
|
||||
RGB888Pixel(KDColor c) :
|
||||
m_red(c.red()),
|
||||
m_green(c.green()),
|
||||
m_blue(c.blue()) {
|
||||
}
|
||||
private:
|
||||
uint8_t m_red;
|
||||
uint8_t m_green;
|
||||
uint8_t m_blue;
|
||||
};
|
||||
static_assert(sizeof(RGB888Pixel) == 3, "RGB888Pixel shall be 3 bytes long");
|
||||
|
||||
void saveImage(const KDColor * pixels, int width, int height, const char * path) {
|
||||
FILE * file = fopen(path, "wb"); // Write in binary mode
|
||||
|
||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
png_infop info = png_create_info_struct(png);
|
||||
png_init_io(png, file);
|
||||
|
||||
png_set_IHDR(png, info,
|
||||
width, height,
|
||||
8, // Number of bits per channel
|
||||
PNG_COLOR_TYPE_RGB,
|
||||
PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
png_write_info(png, info);
|
||||
|
||||
RGB888Pixel * row = new RGB888Pixel[3*width];
|
||||
for (int j=0;j<height;j++) {
|
||||
for (int i=0; i<width; i++) {
|
||||
row[i] = RGB888Pixel(pixels[i+width*j]);
|
||||
}
|
||||
png_write_row(png, reinterpret_cast<png_bytep>(row));
|
||||
}
|
||||
delete row;
|
||||
|
||||
png_write_end(png, NULL);
|
||||
|
||||
png_free_data(png, info, PNG_FREE_ALL, -1); // -1 = all items
|
||||
png_destroy_write_struct(&png, nullptr);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
ION_SIMULATOR_FILES = 1
|
||||
|
||||
ion_src += $(addprefix ion/src/simulator/macos/, \
|
||||
platform_files.mm \
|
||||
)
|
||||
@@ -11,10 +13,8 @@ ion_src += $(addprefix ion/src/simulator/shared/, \
|
||||
clipboard_helper.cpp \
|
||||
collect_registers_x86_64.s \
|
||||
collect_registers.cpp \
|
||||
actions.cpp \
|
||||
haptics.cpp \
|
||||
journal.cpp \
|
||||
state_file.cpp \
|
||||
)
|
||||
|
||||
ifeq ($(EPSILON_TELEMETRY),1)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "actions.h"
|
||||
#include <ion/display.h>
|
||||
#include "framebuffer.h"
|
||||
#include "platform.h"
|
||||
#include "state_file.h"
|
||||
@@ -29,7 +30,11 @@ void loadState() {
|
||||
void takeScreenshot() {
|
||||
const char * path = Platform::filePathForWriting("png");
|
||||
if (path != nullptr) {
|
||||
// Framebuffer::writeToFile(path);
|
||||
Platform::saveImage(
|
||||
Framebuffer::address(),
|
||||
Display::Width, Display::Height,
|
||||
path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../shared/platform.h"
|
||||
#include "../platform.h"
|
||||
#include <SDL.h>
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_MAC
|
||||
@@ -11,42 +11,50 @@ namespace Ion {
|
||||
namespace Simulator {
|
||||
namespace Platform {
|
||||
|
||||
static CGContextRef createABGR8888Context(size_t width, size_t height) {
|
||||
size_t bytesPerPixel = 4;
|
||||
size_t bytesPerRow = bytesPerPixel * width;
|
||||
size_t bitsPerComponent = 8;
|
||||
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGContextRef context = CGBitmapContextCreate(
|
||||
nullptr, // The context will allocate and take ownership of the bitmap buffer
|
||||
width, height,
|
||||
bitsPerComponent, bytesPerRow, colorSpace,
|
||||
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big
|
||||
);
|
||||
if (colorSpace) {
|
||||
CFRelease(colorSpace);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
SDL_Texture * loadImage(SDL_Renderer * renderer, const char * identifier) {
|
||||
CGImageRef cgImage = NULL;
|
||||
CGImageRef image = nullptr;
|
||||
#if TARGET_OS_MAC
|
||||
//http://lists.libsdl.org/pipermail/commits-libsdl.org/2016-December/001235.html
|
||||
[[[NSApp windows] firstObject] setColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||
|
||||
NSImage * nsImage = [NSImage imageNamed:[NSString stringWithUTF8String:identifier]];
|
||||
cgImage = [nsImage CGImageForProposedRect:NULL context:NULL hints:0];
|
||||
image = [nsImage CGImageForProposedRect:NULL context:NULL hints:0];
|
||||
#else
|
||||
cgImage = [[UIImage imageNamed:[NSString stringWithUTF8String:identifier]] CGImage];
|
||||
image = [[UIImage imageNamed:[NSString stringWithUTF8String:identifier]] CGImage];
|
||||
#endif
|
||||
if (cgImage == NULL) {
|
||||
return NULL;
|
||||
if (image == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
size_t width = CGImageGetWidth(cgImage);
|
||||
size_t height = CGImageGetHeight(cgImage);
|
||||
|
||||
size_t bytesPerPixel = 4;
|
||||
size_t bytesPerRow = bytesPerPixel * width;
|
||||
size_t bitsPerComponent = 8;
|
||||
size_t width = CGImageGetWidth(image);
|
||||
size_t height = CGImageGetHeight(image);
|
||||
|
||||
size_t size = height * width * bytesPerPixel;
|
||||
void * bitmapData = malloc(size);
|
||||
memset(bitmapData, 0, size);
|
||||
CGContextRef context = createABGR8888Context(width, height);
|
||||
if (context == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGContextRef context = CGBitmapContextCreate(
|
||||
bitmapData, width, height,
|
||||
bitsPerComponent, bytesPerRow, colorSpace,
|
||||
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big
|
||||
);
|
||||
void * argb8888Pixels = CGBitmapContextGetData(context);
|
||||
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
||||
|
||||
CGContextRelease(context);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
|
||||
|
||||
SDL_Texture * texture = SDL_CreateTexture(
|
||||
renderer,
|
||||
@@ -56,20 +64,64 @@ SDL_Texture * loadImage(SDL_Renderer * renderer, const char * identifier) {
|
||||
height
|
||||
);
|
||||
|
||||
size_t bytesPerPixel = 4;
|
||||
|
||||
SDL_UpdateTexture(
|
||||
texture,
|
||||
NULL,
|
||||
bitmapData,
|
||||
argb8888Pixels,
|
||||
bytesPerPixel * width
|
||||
);
|
||||
|
||||
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
free(bitmapData);
|
||||
CGContextRelease(context);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
class ABGR8888Pixel {
|
||||
public:
|
||||
ABGR8888Pixel(KDColor c) :
|
||||
m_red(c.red()),
|
||||
m_green(c.green()),
|
||||
m_blue(c.blue()),
|
||||
m_alpha(255) {
|
||||
}
|
||||
private:
|
||||
uint8_t m_red;
|
||||
uint8_t m_green;
|
||||
uint8_t m_blue;
|
||||
uint8_t m_alpha;
|
||||
};
|
||||
static_assert(sizeof(ABGR8888Pixel) == 4, "ARGB8888Pixel shall be 4 bytes long");
|
||||
|
||||
void saveImage(const KDColor * pixels, int width, int height, const char * path) {
|
||||
CGContextRef context = createABGR8888Context(width, height);
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
ABGR8888Pixel * argb8888Pixels = static_cast<ABGR8888Pixel *>(CGBitmapContextGetData(context));
|
||||
for (int i=0; i<width*height; i++) {
|
||||
argb8888Pixels[i] = ABGR8888Pixel(pixels[i]);
|
||||
}
|
||||
|
||||
CGImageRef image = CGBitmapContextCreateImage(context);
|
||||
|
||||
CFURLRef url = static_cast<CFURLRef>([NSURL fileURLWithPath:[NSString stringWithUTF8String:path]]);
|
||||
|
||||
CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, NULL);
|
||||
CGImageDestinationAddImage(destination, image, nil);
|
||||
CGImageDestinationFinalize(destination);
|
||||
|
||||
if (destination) {
|
||||
CFRelease(destination);
|
||||
}
|
||||
CGImageRelease(image);
|
||||
CGContextRelease(context);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#include "../actions.h"
|
||||
|
||||
namespace Ion {
|
||||
namespace Simulator {
|
||||
namespace Actions {
|
||||
|
||||
bool actionForEvent(SDL_KeyboardEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void saveState() {
|
||||
}
|
||||
|
||||
void loadState() {
|
||||
}
|
||||
|
||||
void takeScreenshot() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "../platform.h"
|
||||
|
||||
namespace Ion {
|
||||
namespace Simulator {
|
||||
namespace Platform {
|
||||
|
||||
const char * filePathForReading(const char * extension) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char * filePathForWriting(const char * extension) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
#include <ion.h>
|
||||
#include <ion/events.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "journal.h"
|
||||
|
||||
namespace Ion {
|
||||
namespace Simulator {
|
||||
namespace StateFile {
|
||||
|
||||
static constexpr const char * sHeader = "NWSF";
|
||||
static constexpr int sHeaderLength = 4;
|
||||
static constexpr int sVersionLength = 8;
|
||||
|
||||
/* File format: * "NWSF" + "XXXXXXXX" (version) + EVENTS... */
|
||||
|
||||
static inline bool load(FILE * f) {
|
||||
char buffer[sVersionLength+1];
|
||||
|
||||
// Header
|
||||
buffer[sHeaderLength] = 0;
|
||||
if (fread(buffer, sHeaderLength, 1, f) != 1) {
|
||||
return false;
|
||||
}
|
||||
printf("READ\n");
|
||||
if (strcmp(buffer, sHeader) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Software version
|
||||
buffer[sVersionLength] = 0;
|
||||
if (fread(buffer, sVersionLength, 1, f) != 1) {
|
||||
return false;
|
||||
}
|
||||
if (strcmp(buffer, softwareVersion()) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Events
|
||||
Ion::Events::Journal * journal = Journal::replayJournal();
|
||||
while (fread(buffer, 1, 1, f) == 1) {
|
||||
Ion::Events::Event e = Ion::Events::Event(buffer[0]);
|
||||
journal->pushEvent(e);
|
||||
}
|
||||
Ion::Events::replayFrom(journal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void load(const char * filename) {
|
||||
FILE * f = nullptr;
|
||||
if (strcmp(filename, "-") == 0) {
|
||||
f = stdin;
|
||||
} else {
|
||||
f = fopen(filename, "rb");
|
||||
}
|
||||
if (f == nullptr) {
|
||||
return;
|
||||
}
|
||||
load(f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
static inline bool save(FILE * f) {
|
||||
if (fwrite(sHeader, sHeaderLength, 1, f) != 1) {
|
||||
return false;
|
||||
}
|
||||
if (fwrite(softwareVersion(), sVersionLength, 1, f) != 1) {
|
||||
return false;
|
||||
}
|
||||
Ion::Events::Journal * journal = Journal::logJournal();
|
||||
Ion::Events::Event e;
|
||||
while (!journal->isEmpty()) {
|
||||
Ion::Events::Event e = journal->popEvent();
|
||||
uint8_t code = static_cast<uint8_t>(e);
|
||||
if (fwrite(&code, 1, 1, f) != 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void save(const char * filename) {
|
||||
printf("Saving to \"%s\"\n", filename);
|
||||
FILE * f = fopen(filename, "w");
|
||||
if (f == nullptr) {
|
||||
return;
|
||||
}
|
||||
save(f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,14 @@ static inline Event eventFromSDLKeyboardEvent(SDL_KeyboardEvent event) {
|
||||
return Copy;
|
||||
case SDLK_v:
|
||||
return Paste;
|
||||
#if ION_SIMULATOR_FILES
|
||||
case SDLK_s:
|
||||
Simulator::Actions::saveState();
|
||||
return None;
|
||||
case SDLK_p:
|
||||
Simulator::Actions::takeScreenshot();
|
||||
return None;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (event.keysym.mod & KMOD_ALT) {
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace Framebuffer {
|
||||
|
||||
const KDColor * address();
|
||||
void setActive(bool enabled);
|
||||
void writeToFile(const char * filename);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#if EPSILON_SIMULATOR_HAS_LIBPNG
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include <ion/display.h>
|
||||
#include <stdlib.h>
|
||||
#include <png.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
} pixel_t;
|
||||
|
||||
void Ion::Simulator::Framebuffer::writeToFile(const char * filename) {
|
||||
FILE * file = fopen(filename, "wb"); // Write in binary mode
|
||||
//ENSURE(file != NULL, "Opening file %s", filename);
|
||||
|
||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
//ENSURE(png != NULL, "Allocating PNG write structure");
|
||||
|
||||
png_infop info = png_create_info_struct(png);
|
||||
//ENSURE(info != NULL, "Allocating info structure");
|
||||
|
||||
png_init_io(png, file);
|
||||
|
||||
png_set_IHDR(png, info,
|
||||
Ion::Display::Width, Ion::Display::Height,
|
||||
8, // Number of bits per channel
|
||||
PNG_COLOR_TYPE_RGB,
|
||||
PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
png_write_info(png, info);
|
||||
|
||||
static_assert(sizeof(pixel_t) == 3, "pixel_t shall be 3 bytes long (RGB888 format)");
|
||||
pixel_t * row = (pixel_t *)malloc(3*Ion::Display::Width);
|
||||
const KDColor * pixels = address();
|
||||
for (int j=0;j<Ion::Display::Height;j++) {
|
||||
for (int i=0; i<Ion::Display::Width; i++) {
|
||||
KDColor c = pixels[i+Ion::Display::Width*j];
|
||||
row[i].red = c.red();
|
||||
row[i].green = c.green();
|
||||
row[i].blue = c.blue();
|
||||
}
|
||||
png_write_row(png, (png_bytep)row);
|
||||
}
|
||||
free(row);
|
||||
|
||||
png_write_end(png, NULL);
|
||||
|
||||
png_free_data(png, info, PNG_FREE_ALL, -1); // -1 = all items
|
||||
png_destroy_write_struct(&png, (png_infopp)NULL);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -94,6 +94,13 @@ int main(int argc, char * argv[]) {
|
||||
|
||||
bool headless = args.popFlag("--headless");
|
||||
|
||||
#if ION_SIMULATOR_FILES
|
||||
const char * stateFile = args.pop("--load-state-file");
|
||||
if (stateFile) {
|
||||
StateFile::load(stateFile);
|
||||
}
|
||||
#endif
|
||||
|
||||
Random::init();
|
||||
if (!headless) {
|
||||
Journal::init();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define ION_SIMULATOR_PLATFORM_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <kandinsky/color.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Simulator {
|
||||
@@ -9,6 +10,11 @@ namespace Platform {
|
||||
|
||||
SDL_Texture * loadImage(SDL_Renderer * renderer, const char * identifier);
|
||||
const char * languageCode();
|
||||
#if ION_SIMULATOR_FILES
|
||||
const char * filePathForReading(const char * extension);
|
||||
const char * filePathForWriting(const char * extension);
|
||||
void saveImage(const KDColor * pixels, int width, int height, const char * path);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ ion_src += $(addprefix ion/src/simulator/web/, \
|
||||
)
|
||||
|
||||
ion_src += $(addprefix ion/src/simulator/shared/, \
|
||||
dummy/actions.cpp \
|
||||
dummy/language.cpp \
|
||||
dummy/haptics_enabled.cpp \
|
||||
haptics.cpp \
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
ION_SIMULATOR_FILES = 1
|
||||
|
||||
ion_src += $(addprefix ion/src/simulator/windows/, \
|
||||
platform_files.cpp \
|
||||
platform_images.cpp \
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <assert.h>
|
||||
|
||||
/* Loading images using GDI+
|
||||
* On Windows, we decompress JPEG images using GDI+ which is widely available.
|
||||
* On Windows, we manipulate images using GDI+ which is widely available.
|
||||
* Note that this adds an extra runtime dependency (as compared to just SDL),
|
||||
* but this should not be an issue. */
|
||||
|
||||
@@ -36,14 +36,63 @@ static inline HRESULT CreateStreamOnResource(const char * name, LPSTREAM * strea
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Helper class to init/shutdown Gdiplus using RAII
|
||||
class GdiplusSession {
|
||||
public:
|
||||
GdiplusSession() {
|
||||
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
|
||||
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, nullptr);
|
||||
}
|
||||
~GdiplusSession() {
|
||||
Gdiplus::GdiplusShutdown(m_gdiplusToken);
|
||||
}
|
||||
private:
|
||||
ULONG_PTR m_gdiplusToken;
|
||||
};
|
||||
|
||||
// Helper function from MSDN
|
||||
// https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-retrieving-the-class-identifier-for-an-encoder-use
|
||||
int GetEncoderClsid(const WCHAR * format, CLSID * pClsid) {
|
||||
UINT num = 0; // number of image encoders
|
||||
UINT size = 0; // size of the image encoder array in bytes
|
||||
|
||||
Gdiplus::ImageCodecInfo * pImageCodecInfo = nullptr;
|
||||
Gdiplus::GetImageEncodersSize(&num, &size);
|
||||
if (size == 0) {
|
||||
return -1;
|
||||
}
|
||||
pImageCodecInfo = static_cast<Gdiplus::ImageCodecInfo *>(malloc(size));
|
||||
if (pImageCodecInfo == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
|
||||
|
||||
for (UINT i=0; i<num; i++) {
|
||||
if (wcscmp(pImageCodecInfo[i].MimeType, format) == 0) {
|
||||
*pClsid = pImageCodecInfo[i].Clsid;
|
||||
free(pImageCodecInfo);
|
||||
return i; // Success
|
||||
}
|
||||
}
|
||||
|
||||
free(pImageCodecInfo);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static wchar_t * createWideCharArray(const char * src) {
|
||||
int wchars_num = MultiByteToWideChar(CP_UTF8, 0, src, -1, NULL, 0);
|
||||
wchar_t * wstr = new wchar_t[wchars_num];
|
||||
MultiByteToWideChar(CP_UTF8, 0, src, -1, wstr, wchars_num);
|
||||
return wstr;
|
||||
}
|
||||
|
||||
namespace Ion {
|
||||
namespace Simulator {
|
||||
namespace Platform {
|
||||
|
||||
SDL_Texture * loadImage(SDL_Renderer * renderer, const char * identifier) {
|
||||
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
|
||||
ULONG_PTR gdiplusToken;
|
||||
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
|
||||
GdiplusSession session;
|
||||
|
||||
LPSTREAM stream;
|
||||
int resourceID = -1;
|
||||
@@ -87,11 +136,24 @@ SDL_Texture * loadImage(SDL_Renderer * renderer, const char * identifier) {
|
||||
image->UnlockBits(bitmapData);
|
||||
delete bitmapData;
|
||||
delete image;
|
||||
Gdiplus::GdiplusShutdown(gdiplusToken);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void saveImage(const KDColor * pixels, int width, int height, const char * path) {
|
||||
static_assert(sizeof(KDColor) == 2, "KDColor expected to be RGB565");
|
||||
GdiplusSession session;
|
||||
|
||||
Gdiplus::Bitmap bitmap(width, height, 2*width, PixelFormat16bppRGB565, reinterpret_cast<BYTE *>(const_cast<KDColor *>(pixels)));
|
||||
|
||||
CLSID pngClsid;
|
||||
if (GetEncoderClsid(L"image/png", &pngClsid) > 0) {
|
||||
wchar_t * widePath = createWideCharArray(path);
|
||||
bitmap.Save(widePath, &pngClsid, nullptr);
|
||||
delete[] widePath;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user