2023-01-05 13:25:55 +01:00
/**************************************************************************/
/* image_loader_svg.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2017-08-19 04:21:24 +02:00
# include "image_loader_svg.h"
2022-01-13 13:54:19 +01:00
# include "core/os/memory.h"
# include "core/variant/variant.h"
# include <thorvg.h>
2022-08-22 21:07:02 +02:00
HashMap < Color , Color > ImageLoaderSVG : : forced_color_map = HashMap < Color , Color > ( ) ;
void ImageLoaderSVG : : set_forced_color_map ( const HashMap < Color , Color > & p_color_map ) {
forced_color_map = p_color_map ;
}
2022-08-23 13:15:31 +02:00
void ImageLoaderSVG : : _replace_color_property ( const HashMap < Color , Color > & p_color_map , const String & p_prefix , String & r_string ) {
// Replace colors in the SVG based on what is passed in `p_color_map`.
2022-01-13 13:54:19 +01:00
// Used to change the colors of editor icons based on the used theme.
// The strings being replaced are typically of the form:
// fill="#5abbef"
// But can also be 3-letter codes, include alpha, be "none" or a named color
2022-08-23 13:15:31 +02:00
// string ("blue"). So we convert to Godot Color to compare with `p_color_map`.
2022-01-13 13:54:19 +01:00
const int prefix_len = p_prefix . length ( ) ;
int pos = r_string . find ( p_prefix ) ;
while ( pos ! = - 1 ) {
pos + = prefix_len ; // Skip prefix.
int end_pos = r_string . find ( " \" " , pos ) ;
ERR_FAIL_COND_MSG ( end_pos = = - 1 , vformat ( " Malformed SVG string after property \" %s \" . " , p_prefix ) ) ;
const String color_code = r_string . substr ( pos , end_pos - pos ) ;
if ( color_code ! = " none " & & ! color_code . begins_with ( " url( " ) ) {
const Color color = Color ( color_code ) ; // Handles both HTML codes and named colors.
2022-08-23 13:15:31 +02:00
if ( p_color_map . has ( color ) ) {
r_string = r_string . left ( pos ) + " # " + p_color_map [ color ] . to_html ( false ) + r_string . substr ( end_pos ) ;
2017-08-30 20:29:35 +02:00
}
}
2022-01-13 13:54:19 +01:00
// Search for other occurrences.
pos = r_string . find ( p_prefix , pos ) ;
2017-08-30 20:29:35 +02:00
}
}
2017-09-08 04:31:12 +02:00
2023-06-15 01:27:56 +02:00
Ref < Image > ImageLoaderSVG : : load_mem_svg ( const uint8_t * p_svg , int p_size , float p_scale ) {
Ref < Image > img ;
img . instantiate ( ) ;
Error err = create_image_from_utf8_buffer ( img , p_svg , p_size , p_scale , false ) ;
ERR_FAIL_COND_V ( err , Ref < Image > ( ) ) ;
return img ;
}
Error ImageLoaderSVG : : create_image_from_utf8_buffer ( Ref < Image > p_image , const uint8_t * p_buffer , int p_buffer_size , float p_scale , bool p_upsample ) {
2022-11-18 22:47:55 +01:00
ERR_FAIL_COND_V_MSG ( Math : : is_zero_approx ( p_scale ) , ERR_INVALID_PARAMETER , " ImageLoaderSVG: Can't load SVG with a scale of 0. " ) ;
2017-09-08 04:31:12 +02:00
2022-01-13 13:54:19 +01:00
std : : unique_ptr < tvg : : Picture > picture = tvg : : Picture : : gen ( ) ;
2020-01-19 23:21:49 +01:00
2023-06-15 01:27:56 +02:00
tvg : : Result result = picture - > load ( ( const char * ) p_buffer , p_buffer_size , " svg " , true ) ;
2022-01-13 13:54:19 +01:00
if ( result ! = tvg : : Result : : Success ) {
2022-11-18 22:47:55 +01:00
return ERR_INVALID_DATA ;
2020-01-19 23:21:49 +01:00
}
2022-01-13 13:54:19 +01:00
float fw , fh ;
2022-03-13 17:00:05 +01:00
picture - > size ( & fw , & fh ) ;
2017-08-19 09:09:58 +02:00
2023-04-05 19:15:21 +02:00
uint32_t width = MAX ( 1 , round ( fw * p_scale ) ) ;
uint32_t height = MAX ( 1 , round ( fh * p_scale ) ) ;
2022-11-18 22:47:55 +01:00
const uint32_t max_dimension = 16384 ;
if ( width > max_dimension | | height > max_dimension ) {
WARN_PRINT ( vformat (
String : : utf8 ( " ImageLoaderSVG: Target canvas dimensions %d× %d (with scale %.2f) exceed the max supported dimensions %d× %d. The target canvas will be scaled down. " ) ,
width , height , p_scale , max_dimension , max_dimension ) ) ;
width = MIN ( width , max_dimension ) ;
height = MIN ( height , max_dimension ) ;
}
2022-01-13 13:54:19 +01:00
picture - > size ( width , height ) ;
2017-08-19 04:21:24 +02:00
2022-01-13 13:54:19 +01:00
std : : unique_ptr < tvg : : SwCanvas > sw_canvas = tvg : : SwCanvas : : gen ( ) ;
// Note: memalloc here, be sure to memfree before any return.
uint32_t * buffer = ( uint32_t * ) memalloc ( sizeof ( uint32_t ) * width * height ) ;
2017-08-19 04:21:24 +02:00
2022-01-13 13:54:19 +01:00
tvg : : Result res = sw_canvas - > target ( buffer , width , width , height , tvg : : SwCanvas : : ARGB8888_STRAIGHT ) ;
if ( res ! = tvg : : Result : : Success ) {
memfree ( buffer ) ;
2022-11-18 22:47:55 +01:00
ERR_FAIL_V_MSG ( FAILED , " ImageLoaderSVG: Couldn't set target on ThorVG canvas. " ) ;
2020-01-19 23:21:49 +01:00
}
2017-08-19 04:21:24 +02:00
2022-05-10 12:05:52 +02:00
res = sw_canvas - > push ( std : : move ( picture ) ) ;
2022-01-13 13:54:19 +01:00
if ( res ! = tvg : : Result : : Success ) {
memfree ( buffer ) ;
2022-11-18 22:47:55 +01:00
ERR_FAIL_V_MSG ( FAILED , " ImageLoaderSVG: Couldn't insert ThorVG picture on canvas. " ) ;
2022-01-13 13:54:19 +01:00
}
2017-08-19 04:21:24 +02:00
2022-01-13 13:54:19 +01:00
res = sw_canvas - > draw ( ) ;
if ( res ! = tvg : : Result : : Success ) {
memfree ( buffer ) ;
2022-11-18 22:47:55 +01:00
ERR_FAIL_V_MSG ( FAILED , " ImageLoaderSVG: Couldn't draw ThorVG pictures on canvas. " ) ;
2022-01-13 13:54:19 +01:00
}
2017-08-19 04:21:24 +02:00
2022-01-13 13:54:19 +01:00
res = sw_canvas - > sync ( ) ;
if ( res ! = tvg : : Result : : Success ) {
memfree ( buffer ) ;
2022-11-18 22:47:55 +01:00
ERR_FAIL_V_MSG ( FAILED , " ImageLoaderSVG: Couldn't sync ThorVG canvas. " ) ;
2022-01-13 13:54:19 +01:00
}
2017-08-19 04:21:24 +02:00
2022-01-13 13:54:19 +01:00
Vector < uint8_t > image ;
image . resize ( width * height * sizeof ( uint32_t ) ) ;
for ( uint32_t y = 0 ; y < height ; y + + ) {
for ( uint32_t x = 0 ; x < width ; x + + ) {
uint32_t n = buffer [ y * width + x ] ;
const size_t offset = sizeof ( uint32_t ) * width * y + sizeof ( uint32_t ) * x ;
image . write [ offset + 0 ] = ( n > > 16 ) & 0xff ;
image . write [ offset + 1 ] = ( n > > 8 ) & 0xff ;
image . write [ offset + 2 ] = n & 0xff ;
image . write [ offset + 3 ] = ( n > > 24 ) & 0xff ;
}
}
2017-08-19 04:21:24 +02:00
2022-01-13 13:54:19 +01:00
res = sw_canvas - > clear ( true ) ;
memfree ( buffer ) ;
2017-08-19 04:21:24 +02:00
2022-07-22 20:06:19 +02:00
p_image - > set_data ( width , height , false , Image : : FORMAT_RGBA8 , image ) ;
2022-11-18 22:47:55 +01:00
return OK ;
2017-08-19 04:21:24 +02:00
}
2023-06-15 01:27:56 +02:00
Error ImageLoaderSVG : : create_image_from_utf8_buffer ( Ref < Image > p_image , const PackedByteArray & p_buffer , float p_scale , bool p_upsample ) {
return create_image_from_utf8_buffer ( p_image , p_buffer . ptr ( ) , p_buffer . size ( ) , p_scale , p_upsample ) ;
}
2022-12-19 21:42:43 +01:00
Error ImageLoaderSVG : : create_image_from_string ( Ref < Image > p_image , String p_string , float p_scale , bool p_upsample , const HashMap < Color , Color > & p_color_map ) {
if ( p_color_map . size ( ) ) {
_replace_color_property ( p_color_map , " stop-color= \" " , p_string ) ;
_replace_color_property ( p_color_map , " fill= \" " , p_string ) ;
_replace_color_property ( p_color_map , " stroke= \" " , p_string ) ;
}
PackedByteArray bytes = p_string . to_utf8_buffer ( ) ;
return create_image_from_utf8_buffer ( p_image , bytes , p_scale , p_upsample ) ;
}
2017-08-19 04:21:24 +02:00
void ImageLoaderSVG : : get_recognized_extensions ( List < String > * p_extensions ) const {
p_extensions - > push_back ( " svg " ) ;
}
2022-07-08 15:01:02 +02:00
Error ImageLoaderSVG : : load_image ( Ref < Image > p_image , Ref < FileAccess > p_fileaccess , BitField < ImageFormatLoader : : LoaderFlags > p_flags , float p_scale ) {
2022-01-13 13:54:19 +01:00
String svg = p_fileaccess - > get_as_utf8_string ( ) ;
2022-08-22 21:07:02 +02:00
2022-11-18 22:47:55 +01:00
Error err ;
2022-08-22 21:07:02 +02:00
if ( p_flags & FLAG_CONVERT_COLORS ) {
2022-11-18 22:47:55 +01:00
err = create_image_from_string ( p_image , svg , p_scale , false , forced_color_map ) ;
2022-08-22 21:07:02 +02:00
} else {
2022-11-18 22:47:55 +01:00
err = create_image_from_string ( p_image , svg , p_scale , false , HashMap < Color , Color > ( ) ) ;
}
if ( err ! = OK ) {
return err ;
} else if ( p_image - > is_empty ( ) ) {
return ERR_INVALID_DATA ;
2022-08-22 21:07:02 +02:00
}
2022-08-23 12:25:14 +02:00
if ( p_flags & FLAG_FORCE_LINEAR ) {
2022-01-13 13:54:19 +01:00
p_image - > srgb_to_linear ( ) ;
}
return OK ;
2017-08-19 04:21:24 +02:00
}
2023-06-15 01:27:56 +02:00
ImageLoaderSVG : : ImageLoaderSVG ( ) {
Image : : _svg_scalable_mem_loader_func = load_mem_svg ;
}