Last active
October 21, 2023 08:44
-
-
Save damywise/8d8328ad77b9eb2add7d39012864ce38 to your computer and use it in GitHub Desktop.
Using SDL to generate images with dart and isolates.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:ffi'; | |
import 'dart:io'; | |
import 'dart:math'; | |
import 'package:ffi/ffi.dart'; | |
import 'package:sdl2/sdl2.dart'; | |
class FontData { | |
FontData(this.fontSource, this.fontSize); | |
final String fontSource; | |
final int fontSize; | |
} | |
int exportImages(List<String> texts, int outputNumber, bool premium) { | |
final fontData = FontData('assets/fonts/ALS-Script.ttf', 148); | |
for (var i = 0; i < texts.length; i++) { | |
while (texts[0].contains(' ')) { | |
texts[0] = texts[0].replaceAll(RegExp(r'\s{2,}'), ' '); | |
} | |
} | |
final outputDir = 'outputs_KMO$outputNumber'; | |
final cert = {true: 'cert_premium', false: 'cert_normal'}; | |
final backgroundPath = 'assets/${cert[premium]}.jpg'; | |
final img = imgLoad(backgroundPath); | |
if (img == nullptr) { | |
return -1; | |
} | |
final window = sdlCreateWindow( | |
'Render Certs_${texts[0]}', | |
SDL_WINDOWPOS_UNDEFINED, | |
SDL_WINDOWPOS_UNDEFINED, | |
img.ref.w, | |
img.ref.h, | |
SDL_WINDOW_HIDDEN); | |
for (var i = 0; i < texts.length; i++) { | |
final name = texts[i]; | |
renderImage(window, img, fontData, name, | |
'$outputDir${Platform.pathSeparator}$name.jpg', premium); | |
} | |
sdlFreeSurface(img); | |
sdlDestroyWindow(window); | |
return 0; | |
} | |
void renderImage(Pointer<SdlWindow> window, Pointer<SdlSurface> img, | |
FontData fontData, String text, String output, bool premium) { | |
final renderer = sdlCreateRenderer( | |
window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); | |
final tImg = sdlCreateTextureFromSurface(renderer, img); | |
final zw = calloc<Int32>()..value = 0; | |
final zh = calloc<Int32>()..value = 0; | |
sdlQueryTexture(tImg, nullptr, nullptr, zw, zh); | |
final dstRect = | |
Rectangle<double>(0, 0, zw.value.toDouble(), zh.value.toDouble()); | |
final dstRectPointer = dstRect.calloc(); | |
sdlRenderCopy(renderer, tImg, nullptr, dstRectPointer); | |
var surface = sdlCreateRgbSurfaceWithFormat( | |
0, img.ref.w, img.ref.h, 32, SDL_PIXELFORMAT_RGBA8888); | |
sdlRenderReadPixels( | |
renderer, | |
nullptr, | |
surface.ref.format.ref.format, | |
surface.ref.pixels, | |
surface.ref.pitch, | |
); | |
renderText(renderer, fontData, text, premium); | |
sdlRenderReadPixels( | |
renderer, | |
nullptr, | |
surface.ref.format.ref.format, | |
surface.ref.pixels, | |
surface.ref.pitch, | |
); | |
zw.callocFree(); | |
zh.callocFree(); | |
imgSaveJpg(surface, output, 99); | |
sdlDestroyTexture(tImg); | |
sdlFreeSurface(surface); | |
sdlDestroyRenderer(renderer); | |
} | |
void renderText(Pointer<SdlRenderer> renderer, FontData fontData, String text, | |
bool premium) { | |
var font = ttfOpenFont(fontData.fontSource, fontData.fontSize); | |
ttfSetFontHinting(font, TTF_HINTING_NORMAL); | |
final color = premium ? hexToRGBA('000000FF') : hexToRGBA('38241BFF'); | |
final zw = calloc<Int32>()..value = 0; | |
final zh = calloc<Int32>()..value = 0; | |
final textWidth = calloc<Int32>()..value = 0; | |
final textHeight = calloc<Int32>()..value = 0; | |
ttfSizeText(font, text, textWidth, textHeight); | |
var newFontSize = fontData.fontSize; | |
const textMaxHeight = 150; | |
while (textHeight.value > textMaxHeight) { | |
newFontSize -= 1; | |
font = ttfOpenFont(fontData.fontSource, newFontSize); | |
ttfSizeText(font, text, textWidth, textHeight); | |
} | |
final leftMargin = premium ? 300 : 187; | |
final rightMargin = premium ? 300 : 187; | |
final imgWid = premium ? 2000 : 2339; | |
final textMaxWidth = imgWid - leftMargin - rightMargin; | |
var newRectW = textWidth; | |
var newRectH = textHeight; | |
if (newRectW.value > textMaxWidth) { | |
newRectH.value = textHeight.value * textMaxWidth ~/ textWidth.value; | |
newRectW.value = textWidth.value * textMaxWidth ~/ textWidth.value; | |
} | |
final tSurf = ttfRenderTextBlendedWrapped(font, text, | |
(calloc<SdlColor>()..setRgba(color.r, color.g, color.b, color.a)).ref, 0); | |
final tText = sdlCreateTextureFromSurface(renderer, tSurf); | |
final topMargin = | |
(premium ? 660 : 840) + (textMaxHeight - newRectH.value) / 2; | |
sdlQueryTexture(tText, nullptr, nullptr, zw, zh); | |
final dstRect = Rectangle<double>(imgWid / 2 - newRectW.value / 2, topMargin, | |
newRectW.value.toDouble(), newRectH.value.toDouble()) | |
.calloc(); | |
sdlRenderCopy(renderer, tText, nullptr, dstRect); | |
sdlDestroyTexture(tText); | |
sdlFreeSurface(tSurf); | |
} | |
class RGBA { | |
final int r; | |
final int g; | |
final int b; | |
final int a; | |
RGBA(this.r, this.g, this.b, this.a); | |
@override | |
String toString() { | |
return 'RGBA($r, $g, $b, $a)'; | |
} | |
} | |
RGBA hexToRGBA(String hexColor) { | |
hexColor = hexColor.toUpperCase().replaceAll("#", ""); | |
if (hexColor.length == 6) { | |
hexColor = "FF$hexColor"; | |
} | |
int hexVal = int.parse(hexColor, radix: 16); | |
return RGBA( | |
(hexVal >> 24) & 255, | |
(hexVal >> 16) & 255, | |
(hexVal >> 8) & 255, | |
hexVal & 255, | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:ffi'; | |
import 'dart:io'; | |
import 'dart:math'; | |
import 'package:ffi/ffi.dart'; | |
import 'package:sdl2/sdl2.dart'; | |
class FontData { | |
FontData(this.fontSource, this.fontSize); | |
final String fontSource; | |
final int fontSize; | |
} | |
int exportImages(List<String> texts, int outputNumber, bool premium) { | |
final fontData = FontData('assets/fonts/ALS-Script.ttf', 148); | |
for (var i = 0; i < texts.length; i++) { | |
while (texts[0].contains(' ')) { | |
texts[0] = texts[0].replaceAll(RegExp(r'\s{2,}'), ' '); | |
} | |
} | |
final outputDir = 'outputs_KMO$outputNumber'; | |
final cert = {true: 'cert_premium', false: 'cert_normal'}; | |
final backgroundPath = 'assets/${cert[premium]}.jpg'; | |
final img = imgLoad(backgroundPath); | |
if (img == nullptr) { | |
return -1; | |
} | |
final window = sdlCreateWindow( | |
'Render Certs_${texts[0]}', | |
SDL_WINDOWPOS_UNDEFINED, | |
SDL_WINDOWPOS_UNDEFINED, | |
img.ref.w, | |
img.ref.h, | |
SDL_WINDOW_HIDDEN); | |
for (var i = 0; i < texts.length; i++) { | |
final name = texts[i]; | |
renderImage(window, img, fontData, name, | |
'$outputDir${Platform.pathSeparator}$name.jpg', premium); | |
} | |
sdlFreeSurface(img); | |
sdlDestroyWindow(window); | |
return 0; | |
} | |
void renderImage(Pointer<SdlWindow> window, Pointer<SdlSurface> img, | |
FontData fontData, String text, String output, bool premium) { | |
final renderer = sdlCreateRenderer( | |
window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); | |
final tImg = sdlCreateTextureFromSurface(renderer, img); | |
final zw = calloc<Int32>()..value = 0; | |
final zh = calloc<Int32>()..value = 0; | |
sdlQueryTexture(tImg, nullptr, nullptr, zw, zh); | |
final dstRect = | |
Rectangle<double>(0, 0, zw.value.toDouble(), zh.value.toDouble()); | |
final dstRectPointer = dstRect.calloc(); | |
sdlRenderCopy(renderer, tImg, nullptr, dstRectPointer); | |
var surface = sdlCreateRgbSurfaceWithFormat( | |
0, img.ref.w, img.ref.h, 32, SDL_PIXELFORMAT_RGBA8888); | |
sdlRenderReadPixels( | |
renderer, | |
nullptr, | |
surface.ref.format.ref.format, | |
surface.ref.pixels, | |
surface.ref.pitch, | |
); | |
renderText(renderer, fontData, text, premium); | |
sdlRenderReadPixels( | |
renderer, | |
nullptr, | |
surface.ref.format.ref.format, | |
surface.ref.pixels, | |
surface.ref.pitch, | |
); | |
zw.callocFree(); | |
zh.callocFree(); | |
imgSaveJpg(surface, output, 99); | |
sdlDestroyTexture(tImg); | |
sdlFreeSurface(surface); | |
sdlDestroyRenderer(renderer); | |
} | |
void renderText(Pointer<SdlRenderer> renderer, FontData fontData, String text, | |
bool premium) { | |
var font = ttfOpenFont(fontData.fontSource, fontData.fontSize); | |
ttfSetFontHinting(font, TTF_HINTING_NORMAL); | |
final color = premium ? hexToRGBA('000000FF') : hexToRGBA('38241BFF'); | |
final zw = calloc<Int32>()..value = 0; | |
final zh = calloc<Int32>()..value = 0; | |
final textWidth = calloc<Int32>()..value = 0; | |
final textHeight = calloc<Int32>()..value = 0; | |
ttfSizeText(font, text, textWidth, textHeight); | |
var newFontSize = fontData.fontSize; | |
const textMaxHeight = 150; | |
while (textHeight.value > textMaxHeight) { | |
newFontSize -= 1; | |
font = ttfOpenFont(fontData.fontSource, newFontSize); | |
ttfSizeText(font, text, textWidth, textHeight); | |
} | |
final leftMargin = premium ? 300 : 187; | |
final rightMargin = premium ? 300 : 187; | |
final imgWid = premium ? 2000 : 2339; | |
final textMaxWidth = imgWid - leftMargin - rightMargin; | |
var newRectW = textWidth; | |
var newRectH = textHeight; | |
if (newRectW.value > textMaxWidth) { | |
newRectH.value = textHeight.value * textMaxWidth ~/ textWidth.value; | |
newRectW.value = textWidth.value * textMaxWidth ~/ textWidth.value; | |
} | |
final tSurf = ttfRenderTextBlendedWrapped(font, text, | |
(calloc<SdlColor>()..setRgba(color.r, color.g, color.b, color.a)).ref, 0); | |
final tText = sdlCreateTextureFromSurface(renderer, tSurf); | |
final topMargin = | |
(premium ? 660 : 840) + (textMaxHeight - newRectH.value) / 2; | |
sdlQueryTexture(tText, nullptr, nullptr, zw, zh); | |
final dstRect = Rectangle<double>(imgWid / 2 - newRectW.value / 2, topMargin, | |
newRectW.value.toDouble(), newRectH.value.toDouble()) | |
.calloc(); | |
sdlRenderCopy(renderer, tText, nullptr, dstRect); | |
sdlDestroyTexture(tText); | |
sdlFreeSurface(tSurf); | |
} | |
class RGBA { | |
final int r; | |
final int g; | |
final int b; | |
final int a; | |
RGBA(this.r, this.g, this.b, this.a); | |
@override | |
String toString() { | |
return 'RGBA($r, $g, $b, $a)'; | |
} | |
} | |
RGBA hexToRGBA(String hexColor) { | |
hexColor = hexColor.toUpperCase().replaceAll("#", ""); | |
if (hexColor.length == 6) { | |
hexColor = "FF$hexColor"; | |
} | |
int hexVal = int.parse(hexColor, radix: 16); | |
return RGBA( | |
(hexVal >> 24) & 255, | |
(hexVal >> 16) & 255, | |
(hexVal >> 8) & 255, | |
hexVal & 255, | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment