2014-02-10 02:10:30 +01:00
/**************************************************************************/
/* image.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
2014-02-10 02:10:30 +01:00
# include "image.h"
2017-01-16 08:04:19 +01:00
2022-01-18 13:39:55 +01:00
# include "core/error/error_list.h"
2020-11-07 23:33:38 +01:00
# include "core/error/error_macros.h"
2014-02-10 02:10:30 +01:00
# include "core/io/image_loader.h"
2018-09-11 18:13:45 +02:00
# include "core/io/resource_loader.h"
# include "core/math/math_funcs.h"
2020-11-07 23:33:38 +01:00
# include "core/string/print_string.h"
# include "core/templates/hash_map.h"
2022-01-18 13:39:55 +01:00
# include "core/variant/dictionary.h"
2017-01-16 08:04:19 +01:00
2014-02-10 02:10:30 +01:00
# include <stdio.h>
2022-01-18 13:39:55 +01:00
# include <cmath>
2014-02-10 02:10:30 +01:00
2015-10-21 14:50:44 +02:00
const char * Image : : format_names [ Image : : FORMAT_MAX ] = {
2016-10-03 21:33:42 +02:00
" Lum8 " , //luminance
" LumAlpha8 " , //luminance-alpha
" Red8 " ,
" RedGreen " ,
" RGB8 " ,
" RGBA8 " ,
" RGBA4444 " ,
" RGBA5551 " ,
" RFloat " , //float
" RGFloat " ,
" RGBFloat " ,
" RGBAFloat " ,
" RHalf " , //half float
" RGHalf " ,
" RGBHalf " ,
" RGBAHalf " ,
2017-05-27 02:49:49 +02:00
" RGBE9995 " ,
" DXT1 RGB8 " , //s3tc
" DXT3 RGBA8 " ,
" DXT5 RGBA8 " ,
" RGTC Red8 " ,
" RGTC RedGreen8 " ,
2016-10-03 21:33:42 +02:00
" BPTC_RGBA " ,
" BPTC_RGBF " ,
" BPTC_RGBFU " ,
" ETC " , //etc1
" ETC2_R11 " , //etc2
" ETC2_R11S " , //signed", NOT srgb.
" ETC2_RG11 " ,
" ETC2_RG11S " ,
" ETC2_RGB8 " ,
" ETC2_RGBA8 " ,
" ETC2_RGB8A1 " ,
2019-09-27 04:16:44 +02:00
" ETC2_RA_AS_RG " ,
" FORMAT_DXT5_RA_AS_RG " ,
2023-01-25 12:17:11 +01:00
" ASTC_4x4 " ,
" ASTC_4x4_HDR " ,
" ASTC_8x8 " ,
" ASTC_8x8_HDR " ,
2015-10-21 14:50:44 +02:00
} ;
2020-04-02 01:20:12 +02:00
SavePNGFunc Image : : save_png_func = nullptr ;
Implement Running Godot as Movie Writer
* Allows running the game in "movie writer" mode.
* It ensures entirely stable framerate, so your run can be saved stable and with proper sound (which is impossible if your CPU/GPU can't sustain doing this in real-time).
* If disabling vsync, it can save movies faster than the game is run, but if you want to control the interaction it can get difficult.
* Implements a simple, default MJPEG writer.
This new features has two main use cases, which have high demand:
* Saving game videos in high quality and ensuring the frame rate is *completely* stable, always.
* Using Godot as a tool to make movies and animations (which is ideal if you want interaction, or creating them procedurally. No other software is as good for this).
**Note**: This feature **IS NOT** for capturing real-time footage. Use something like OBS, SimpleScreenRecorder or FRAPS to achieve that, as they do a much better job at intercepting the compositor than Godot can probably do using Vulkan or OpenGL natively. If your game runs near real-time when capturing, you can still use this feature but it will play no sound (sound will be saved directly).
Usage:
$ godot --write-movie movie.avi [scene_file.tscn]
Missing:
* Options for configuring video writing via GLOBAL_DEF
* UI Menu for launching with this mode from the editor.
* Add to list of command line options.
* Add a feature tag to override configurations when movie writing (fantastic for saving videos with highest quality settings).
2022-06-17 00:55:19 +02:00
SaveJPGFunc Image : : save_jpg_func = nullptr ;
2020-04-02 01:20:12 +02:00
SaveEXRFunc Image : : save_exr_func = nullptr ;
2014-02-10 02:10:30 +01:00
2020-04-02 01:20:12 +02:00
SavePNGBufferFunc Image : : save_png_buffer_func = nullptr ;
2022-06-20 03:55:47 +02:00
SaveEXRBufferFunc Image : : save_exr_buffer_func = nullptr ;
Implement Running Godot as Movie Writer
* Allows running the game in "movie writer" mode.
* It ensures entirely stable framerate, so your run can be saved stable and with proper sound (which is impossible if your CPU/GPU can't sustain doing this in real-time).
* If disabling vsync, it can save movies faster than the game is run, but if you want to control the interaction it can get difficult.
* Implements a simple, default MJPEG writer.
This new features has two main use cases, which have high demand:
* Saving game videos in high quality and ensuring the frame rate is *completely* stable, always.
* Using Godot as a tool to make movies and animations (which is ideal if you want interaction, or creating them procedurally. No other software is as good for this).
**Note**: This feature **IS NOT** for capturing real-time footage. Use something like OBS, SimpleScreenRecorder or FRAPS to achieve that, as they do a much better job at intercepting the compositor than Godot can probably do using Vulkan or OpenGL natively. If your game runs near real-time when capturing, you can still use this feature but it will play no sound (sound will be saved directly).
Usage:
$ godot --write-movie movie.avi [scene_file.tscn]
Missing:
* Options for configuring video writing via GLOBAL_DEF
* UI Menu for launching with this mode from the editor.
* Add to list of command line options.
* Add a feature tag to override configurations when movie writing (fantastic for saving videos with highest quality settings).
2022-06-17 00:55:19 +02:00
SaveJPGBufferFunc Image : : save_jpg_buffer_func = nullptr ;
2019-10-31 23:54:21 +01:00
2022-06-04 23:05:55 +02:00
SaveWebPFunc Image : : save_webp_func = nullptr ;
SaveWebPBufferFunc Image : : save_webp_buffer_func = nullptr ;
2021-09-08 10:16:11 +02:00
void Image : : _put_pixelb ( int p_x , int p_y , uint32_t p_pixel_size , uint8_t * p_data , const uint8_t * p_pixel ) {
uint32_t ofs = ( p_y * width + p_x ) * p_pixel_size ;
memcpy ( p_data + ofs , p_pixel , p_pixel_size ) ;
2014-02-10 02:10:30 +01:00
}
2021-09-08 10:16:11 +02:00
void Image : : _get_pixelb ( int p_x , int p_y , uint32_t p_pixel_size , const uint8_t * p_data , uint8_t * p_pixel ) {
uint32_t ofs = ( p_y * width + p_x ) * p_pixel_size ;
memcpy ( p_pixel , p_data + ofs , p_pixel_size ) ;
2016-10-03 21:33:42 +02:00
}
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
int Image : : get_format_pixel_size ( Format p_format ) {
switch ( p_format ) {
case FORMAT_L8 :
return 1 ; //luminance
case FORMAT_LA8 :
return 2 ; //luminance-alpha
2020-05-10 13:00:47 +02:00
case FORMAT_R8 :
return 1 ;
case FORMAT_RG8 :
return 2 ;
case FORMAT_RGB8 :
return 3 ;
case FORMAT_RGBA8 :
return 4 ;
case FORMAT_RGBA4444 :
return 2 ;
case FORMAT_RGB565 :
return 2 ;
2016-10-03 21:33:42 +02:00
case FORMAT_RF :
return 4 ; //float
2020-05-10 13:00:47 +02:00
case FORMAT_RGF :
return 8 ;
case FORMAT_RGBF :
return 12 ;
case FORMAT_RGBAF :
return 16 ;
2016-10-03 21:33:42 +02:00
case FORMAT_RH :
return 2 ; //half float
2020-05-10 13:00:47 +02:00
case FORMAT_RGH :
return 4 ;
case FORMAT_RGBH :
return 6 ;
case FORMAT_RGBAH :
return 8 ;
case FORMAT_RGBE9995 :
return 4 ;
2016-10-03 21:33:42 +02:00
case FORMAT_DXT1 :
return 1 ; //s3tc bc1
case FORMAT_DXT3 :
return 1 ; //bc2
case FORMAT_DXT5 :
return 1 ; //bc3
2017-05-27 02:49:49 +02:00
case FORMAT_RGTC_R :
2016-10-03 21:33:42 +02:00
return 1 ; //bc4
2017-05-27 02:49:49 +02:00
case FORMAT_RGTC_RG :
2016-10-03 21:33:42 +02:00
return 1 ; //bc5
case FORMAT_BPTC_RGBA :
return 1 ; //btpc bc6h
case FORMAT_BPTC_RGBF :
return 1 ; //float /
case FORMAT_BPTC_RGBFU :
return 1 ; //unsigned float
case FORMAT_ETC :
return 1 ; //etc1
case FORMAT_ETC2_R11 :
return 1 ; //etc2
case FORMAT_ETC2_R11S :
return 1 ; //signed: return 1; NOT srgb.
2020-05-10 13:00:47 +02:00
case FORMAT_ETC2_RG11 :
return 1 ;
case FORMAT_ETC2_RG11S :
return 1 ;
case FORMAT_ETC2_RGB8 :
return 1 ;
case FORMAT_ETC2_RGBA8 :
return 1 ;
case FORMAT_ETC2_RGB8A1 :
return 1 ;
case FORMAT_ETC2_RA_AS_RG :
return 1 ;
case FORMAT_DXT5_RA_AS_RG :
return 1 ;
2022-09-05 21:11:34 +02:00
case FORMAT_ASTC_4x4 :
return 1 ;
case FORMAT_ASTC_4x4_HDR :
return 1 ;
case FORMAT_ASTC_8x8 :
return 1 ;
case FORMAT_ASTC_8x8_HDR :
return 1 ;
2016-10-03 21:33:42 +02:00
case FORMAT_MAX : {
}
}
return 0 ;
}
void Image : : get_format_min_pixel_size ( Format p_format , int & r_w , int & r_h ) {
switch ( p_format ) {
case FORMAT_DXT1 : //s3tc bc1
case FORMAT_DXT3 : //bc2
case FORMAT_DXT5 : //bc3
2017-05-27 02:49:49 +02:00
case FORMAT_RGTC_R : //bc4
case FORMAT_RGTC_RG : { //bc5 case case FORMAT_DXT1:
2016-10-03 21:33:42 +02:00
r_w = 4 ;
r_h = 4 ;
2014-02-10 02:10:30 +01:00
} break ;
2016-10-03 21:33:42 +02:00
case FORMAT_ETC : {
r_w = 4 ;
r_h = 4 ;
} break ;
case FORMAT_BPTC_RGBA :
case FORMAT_BPTC_RGBF :
case FORMAT_BPTC_RGBFU : {
r_w = 4 ;
r_h = 4 ;
2014-02-10 02:10:30 +01:00
} break ;
2016-10-03 21:33:42 +02:00
case FORMAT_ETC2_R11 : //etc2
case FORMAT_ETC2_R11S : //signed: NOT srgb.
case FORMAT_ETC2_RG11 :
case FORMAT_ETC2_RG11S :
case FORMAT_ETC2_RGB8 :
case FORMAT_ETC2_RGBA8 :
2019-09-27 04:16:44 +02:00
case FORMAT_ETC2_RGB8A1 :
case FORMAT_ETC2_RA_AS_RG :
case FORMAT_DXT5_RA_AS_RG : {
2016-10-03 21:33:42 +02:00
r_w = 4 ;
r_h = 4 ;
2014-02-10 02:10:30 +01:00
} break ;
2022-09-05 21:11:34 +02:00
case FORMAT_ASTC_4x4 :
case FORMAT_ASTC_4x4_HDR : {
r_w = 4 ;
r_h = 4 ;
} break ;
case FORMAT_ASTC_8x8 :
case FORMAT_ASTC_8x8_HDR : {
r_w = 8 ;
r_h = 8 ;
2014-02-10 02:10:30 +01:00
2022-09-05 21:11:34 +02:00
} break ;
2016-10-03 21:33:42 +02:00
default : {
r_w = 1 ;
r_h = 1 ;
} break ;
2014-02-10 02:10:30 +01:00
}
}
2016-10-03 21:33:42 +02:00
int Image : : get_format_pixel_rshift ( Format p_format ) {
2022-09-05 21:11:34 +02:00
if ( p_format = = FORMAT_ASTC_8x8 ) {
return 2 ;
} else if ( p_format = = FORMAT_DXT1 | | p_format = = FORMAT_RGTC_R | | p_format = = FORMAT_ETC | | p_format = = FORMAT_ETC2_R11 | | p_format = = FORMAT_ETC2_R11S | | p_format = = FORMAT_ETC2_RGB8 | | p_format = = FORMAT_ETC2_RGB8A1 ) {
2016-10-03 21:33:42 +02:00
return 1 ;
2020-05-14 16:41:43 +02:00
} else {
2016-10-03 21:33:42 +02:00
return 0 ;
2020-05-14 16:41:43 +02:00
}
2016-10-03 21:33:42 +02:00
}
2017-05-27 02:49:49 +02:00
int Image : : get_format_block_size ( Format p_format ) {
switch ( p_format ) {
case FORMAT_DXT1 : //s3tc bc1
case FORMAT_DXT3 : //bc2
case FORMAT_DXT5 : //bc3
case FORMAT_RGTC_R : //bc4
case FORMAT_RGTC_RG : { //bc5 case case FORMAT_DXT1:
return 4 ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_ETC : {
return 4 ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_BPTC_RGBA :
case FORMAT_BPTC_RGBF :
case FORMAT_BPTC_RGBFU : {
return 4 ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_ETC2_R11 : //etc2
case FORMAT_ETC2_R11S : //signed: NOT srgb.
case FORMAT_ETC2_RG11 :
case FORMAT_ETC2_RG11S :
case FORMAT_ETC2_RGB8 :
case FORMAT_ETC2_RGBA8 :
2019-09-27 04:16:44 +02:00
case FORMAT_ETC2_RGB8A1 :
case FORMAT_ETC2_RA_AS_RG : //used to make basis universal happy
case FORMAT_DXT5_RA_AS_RG : //used to make basis universal happy
{
2017-05-27 02:49:49 +02:00
return 4 ;
2019-07-20 08:09:57 +02:00
}
2022-09-05 21:11:34 +02:00
case FORMAT_ASTC_4x4 :
case FORMAT_ASTC_4x4_HDR : {
return 4 ;
}
case FORMAT_ASTC_8x8 :
case FORMAT_ASTC_8x8_HDR : {
return 8 ;
}
2017-05-27 02:49:49 +02:00
default : {
}
}
return 1 ;
}
2014-02-10 02:10:30 +01:00
void Image : : _get_mipmap_offset_and_size ( int p_mipmap , int & r_offset , int & r_width , int & r_height ) const {
int w = width ;
int h = height ;
int ofs = 0 ;
int pixel_size = get_format_pixel_size ( format ) ;
int pixel_rshift = get_format_pixel_rshift ( format ) ;
2017-05-27 02:49:49 +02:00
int block = get_format_block_size ( format ) ;
2014-02-10 02:10:30 +01:00
int minw , minh ;
2016-10-03 21:33:42 +02:00
get_format_min_pixel_size ( format , minw , minh ) ;
2017-03-05 16:44:50 +01:00
2014-02-10 02:10:30 +01:00
for ( int i = 0 ; i < p_mipmap ; i + + ) {
2017-05-27 02:49:49 +02:00
int bw = w % block ! = 0 ? w + ( block - w % block ) : w ;
int bh = h % block ! = 0 ? h + ( block - h % block ) : h ;
int s = bw * bh ;
2014-02-10 02:10:30 +01:00
s * = pixel_size ;
s > > = pixel_rshift ;
ofs + = s ;
w = MAX ( minw , w > > 1 ) ;
h = MAX ( minh , h > > 1 ) ;
}
r_offset = ofs ;
r_width = w ;
r_height = h ;
}
2018-10-03 17:27:22 +02:00
2014-02-10 02:10:30 +01:00
int Image : : get_mipmap_offset ( int p_mipmap ) const {
2017-02-06 04:38:39 +01:00
ERR_FAIL_INDEX_V ( p_mipmap , get_mipmap_count ( ) + 1 , - 1 ) ;
2014-02-10 02:10:30 +01:00
int ofs , w , h ;
_get_mipmap_offset_and_size ( p_mipmap , ofs , w , h ) ;
return ofs ;
}
2020-01-27 00:09:40 +01:00
int Image : : get_mipmap_byte_size ( int p_mipmap ) const {
ERR_FAIL_INDEX_V ( p_mipmap , get_mipmap_count ( ) + 1 , - 1 ) ;
int ofs , w , h ;
_get_mipmap_offset_and_size ( p_mipmap , ofs , w , h ) ;
int ofs2 ;
_get_mipmap_offset_and_size ( p_mipmap + 1 , ofs2 , w , h ) ;
return ofs2 - ofs ;
}
2014-02-10 02:10:30 +01:00
void Image : : get_mipmap_offset_and_size ( int p_mipmap , int & r_ofs , int & r_size ) const {
int ofs , w , h ;
_get_mipmap_offset_and_size ( p_mipmap , ofs , w , h ) ;
int ofs2 ;
_get_mipmap_offset_and_size ( p_mipmap + 1 , ofs2 , w , h ) ;
r_ofs = ofs ;
r_size = ofs2 - ofs ;
}
2015-04-07 02:48:20 +02:00
void Image : : get_mipmap_offset_size_and_dimensions ( int p_mipmap , int & r_ofs , int & r_size , int & w , int & h ) const {
int ofs ;
_get_mipmap_offset_and_size ( p_mipmap , ofs , w , h ) ;
int ofs2 , w2 , h2 ;
_get_mipmap_offset_and_size ( p_mipmap + 1 , ofs2 , w2 , h2 ) ;
r_ofs = ofs ;
r_size = ofs2 - ofs ;
}
2020-09-09 17:40:51 +02:00
Image : : Image3DValidateError Image : : validate_3d_image ( Image : : Format p_format , int p_width , int p_height , int p_depth , bool p_mipmaps , const Vector < Ref < Image > > & p_images ) {
int w = p_width ;
int h = p_height ;
int d = p_depth ;
int arr_ofs = 0 ;
while ( true ) {
for ( int i = 0 ; i < d ; i + + ) {
int idx = i + arr_ofs ;
if ( idx > = p_images . size ( ) ) {
return VALIDATE_3D_ERR_MISSING_IMAGES ;
}
2020-12-15 13:04:21 +01:00
if ( p_images [ idx ] . is_null ( ) | | p_images [ idx ] - > is_empty ( ) ) {
2020-09-09 17:40:51 +02:00
return VALIDATE_3D_ERR_IMAGE_EMPTY ;
}
if ( p_images [ idx ] - > get_format ( ) ! = p_format ) {
return VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH ;
}
if ( p_images [ idx ] - > get_width ( ) ! = w | | p_images [ idx ] - > get_height ( ) ! = h ) {
return VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH ;
}
if ( p_images [ idx ] - > has_mipmaps ( ) ) {
return VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS ;
}
}
arr_ofs + = d ;
if ( ! p_mipmaps ) {
break ;
}
if ( w = = 1 & & h = = 1 & & d = = 1 ) {
break ;
}
w = MAX ( 1 , w > > 1 ) ;
h = MAX ( 1 , h > > 1 ) ;
d = MAX ( 1 , d > > 1 ) ;
}
if ( arr_ofs ! = p_images . size ( ) ) {
return VALIDATE_3D_ERR_EXTRA_IMAGES ;
}
return VALIDATE_3D_OK ;
}
String Image : : get_3d_image_validation_error_text ( Image3DValidateError p_error ) {
switch ( p_error ) {
case VALIDATE_3D_OK : {
2022-03-28 15:24:14 +02:00
return " Ok " ;
2020-09-09 17:40:51 +02:00
} break ;
case VALIDATE_3D_ERR_IMAGE_EMPTY : {
2022-03-28 15:24:14 +02:00
return " Empty Image found " ;
2020-09-09 17:40:51 +02:00
} break ;
case VALIDATE_3D_ERR_MISSING_IMAGES : {
2022-03-28 15:24:14 +02:00
return " Missing Images " ;
2020-09-09 17:40:51 +02:00
} break ;
case VALIDATE_3D_ERR_EXTRA_IMAGES : {
2022-03-28 15:24:14 +02:00
return " Too many Images " ;
2020-09-09 17:40:51 +02:00
} break ;
case VALIDATE_3D_ERR_IMAGE_SIZE_MISMATCH : {
2022-03-28 15:24:14 +02:00
return " Image size mismatch " ;
2020-09-09 17:40:51 +02:00
} break ;
case VALIDATE_3D_ERR_IMAGE_FORMAT_MISMATCH : {
2022-03-28 15:24:14 +02:00
return " Image format mismatch " ;
2020-09-09 17:40:51 +02:00
} break ;
case VALIDATE_3D_ERR_IMAGE_HAS_MIPMAPS : {
2022-03-28 15:24:14 +02:00
return " Image has included mipmaps " ;
2020-09-09 17:40:51 +02:00
} break ;
}
return String ( ) ;
}
2016-10-03 21:33:42 +02:00
int Image : : get_width ( ) const {
return width ;
2014-02-10 02:10:30 +01:00
}
2016-10-03 21:33:42 +02:00
int Image : : get_height ( ) const {
return height ;
2014-02-10 02:10:30 +01:00
}
2022-09-01 11:34:15 +02:00
Size2i Image : : get_size ( ) const {
return Size2i ( width , height ) ;
2017-08-26 13:45:03 +02:00
}
2016-10-03 21:33:42 +02:00
bool Image : : has_mipmaps ( ) const {
return mipmaps ;
2014-02-10 02:10:30 +01:00
}
2016-10-03 21:33:42 +02:00
int Image : : get_mipmap_count ( ) const {
2020-05-14 16:41:43 +02:00
if ( mipmaps ) {
2016-10-03 21:33:42 +02:00
return get_image_required_mipmaps ( width , height , format ) ;
2020-05-14 16:41:43 +02:00
} else {
2016-10-03 21:33:42 +02:00
return 0 ;
2020-05-14 16:41:43 +02:00
}
2016-10-03 21:33:42 +02:00
}
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
//using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers
template < uint32_t read_bytes , bool read_alpha , uint32_t write_bytes , bool write_alpha , bool read_gray , bool write_gray >
static void _convert ( int p_width , int p_height , const uint8_t * p_src , uint8_t * p_dst ) {
2023-05-24 22:00:47 +02:00
constexpr uint32_t max_bytes = MAX ( read_bytes , write_bytes ) ;
2018-04-21 16:35:23 +02:00
2016-10-03 21:33:42 +02:00
for ( int y = 0 ; y < p_height ; y + + ) {
for ( int x = 0 ; x < p_width ; x + + ) {
const uint8_t * rofs = & p_src [ ( ( y * p_width ) + x ) * ( read_bytes + ( read_alpha ? 1 : 0 ) ) ] ;
uint8_t * wofs = & p_dst [ ( ( y * p_width ) + x ) * ( write_bytes + ( write_alpha ? 1 : 0 ) ) ] ;
2014-02-10 02:10:30 +01:00
2022-05-11 10:12:31 +02:00
uint8_t rgba [ 4 ] = { 0 , 0 , 0 , 255 } ;
2014-02-10 02:10:30 +01:00
2022-09-29 08:18:07 +02:00
if constexpr ( read_gray ) {
2016-10-03 21:33:42 +02:00
rgba [ 0 ] = rofs [ 0 ] ;
rgba [ 1 ] = rofs [ 0 ] ;
rgba [ 2 ] = rofs [ 0 ] ;
} else {
2018-04-21 16:35:23 +02:00
for ( uint32_t i = 0 ; i < max_bytes ; i + + ) {
2016-10-03 21:33:42 +02:00
rgba [ i ] = ( i < read_bytes ) ? rofs [ i ] : 0 ;
}
}
2014-02-10 02:10:30 +01:00
2022-09-29 08:18:07 +02:00
if constexpr ( read_alpha | | write_alpha ) {
2016-10-03 21:33:42 +02:00
rgba [ 3 ] = read_alpha ? rofs [ read_bytes ] : 255 ;
}
2014-02-10 02:10:30 +01:00
2022-09-29 08:18:07 +02:00
if constexpr ( write_gray ) {
2023-05-24 22:00:47 +02:00
// REC.709
const uint8_t luminance = ( 13938U * rgba [ 0 ] + 46869U * rgba [ 1 ] + 4729U * rgba [ 2 ] + 32768U ) > > 16U ;
wofs [ 0 ] = luminance ;
2016-10-03 21:33:42 +02:00
} else {
for ( uint32_t i = 0 ; i < write_bytes ; i + + ) {
wofs [ i ] = rgba [ i ] ;
}
}
2014-02-10 02:10:30 +01:00
2022-09-29 08:18:07 +02:00
if constexpr ( write_alpha ) {
2016-10-03 21:33:42 +02:00
wofs [ write_bytes ] = rgba [ 3 ] ;
}
}
}
2014-02-10 02:10:30 +01:00
}
void Image : : convert ( Format p_new_format ) {
2023-11-12 04:17:56 +01:00
ERR_FAIL_INDEX_MSG ( p_new_format , FORMAT_MAX , " The Image format specified ( " + itos ( p_new_format ) + " ) is out of range. See Image's Format enum. " ) ;
2020-05-14 16:41:43 +02:00
if ( data . size ( ) = = 0 ) {
2014-02-10 02:10:30 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-14 16:41:43 +02:00
if ( p_new_format = = format ) {
2014-02-10 02:10:30 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2023-03-02 15:46:06 +01:00
// Includes the main image.
const int mipmap_count = get_mipmap_count ( ) + 1 ;
2017-05-27 02:49:49 +02:00
if ( format > FORMAT_RGBE9995 | | p_new_format > FORMAT_RGBE9995 ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_MSG ( " Cannot convert to <-> from compressed formats. Use compress() and decompress() instead. " ) ;
2017-05-27 02:49:49 +02:00
} else if ( format > FORMAT_RGBA8 | | p_new_format > FORMAT_RGBA8 ) {
//use put/set pixel which is slower but works with non byte formats
2023-03-02 15:46:06 +01:00
Image new_img ( width , height , mipmaps , p_new_format ) ;
for ( int mip = 0 ; mip < mipmap_count ; mip + + ) {
Ref < Image > src_mip = get_image_from_mipmap ( mip ) ;
Ref < Image > new_mip = new_img . get_image_from_mipmap ( mip ) ;
2017-05-27 02:49:49 +02:00
2023-03-02 15:46:06 +01:00
for ( int y = 0 ; y < src_mip - > height ; y + + ) {
for ( int x = 0 ; x < src_mip - > width ; x + + ) {
new_mip - > set_pixel ( x , y , src_mip - > get_pixel ( x , y ) ) ;
}
2017-05-27 02:49:49 +02:00
}
2023-03-02 15:46:06 +01:00
int mip_offset = 0 ;
int mip_size = 0 ;
new_img . get_mipmap_offset_and_size ( mip , mip_offset , mip_size ) ;
memcpy ( new_img . data . ptrw ( ) + mip_offset , new_mip - > data . ptr ( ) , mip_size ) ;
2017-05-27 02:49:49 +02:00
}
_copy_internals_from ( new_img ) ;
return ;
2014-02-10 02:10:30 +01:00
}
2023-03-02 15:46:06 +01:00
Image new_img ( width , height , mipmaps , p_new_format ) ;
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
int conversion_type = format | p_new_format < < 8 ;
2017-03-05 16:44:50 +01:00
2023-03-02 15:46:06 +01:00
for ( int mip = 0 ; mip < mipmap_count ; mip + + ) {
int mip_offset = 0 ;
int mip_size = 0 ;
int mip_width = 0 ;
int mip_height = 0 ;
get_mipmap_offset_size_and_dimensions ( mip , mip_offset , mip_size , mip_width , mip_height ) ;
const uint8_t * rptr = data . ptr ( ) + mip_offset ;
uint8_t * wptr = new_img . data . ptrw ( ) + new_img . get_mipmap_offset ( mip ) ;
switch ( conversion_type ) {
case FORMAT_L8 | ( FORMAT_LA8 < < 8 ) :
_convert < 1 , false , 1 , true , true , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_L8 | ( FORMAT_R8 < < 8 ) :
_convert < 1 , false , 1 , false , true , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_L8 | ( FORMAT_RG8 < < 8 ) :
_convert < 1 , false , 2 , false , true , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_L8 | ( FORMAT_RGB8 < < 8 ) :
_convert < 1 , false , 3 , false , true , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_L8 | ( FORMAT_RGBA8 < < 8 ) :
_convert < 1 , false , 3 , true , true , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_LA8 | ( FORMAT_L8 < < 8 ) :
_convert < 1 , true , 1 , false , true , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_LA8 | ( FORMAT_R8 < < 8 ) :
_convert < 1 , true , 1 , false , true , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_LA8 | ( FORMAT_RG8 < < 8 ) :
_convert < 1 , true , 2 , false , true , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_LA8 | ( FORMAT_RGB8 < < 8 ) :
_convert < 1 , true , 3 , false , true , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_LA8 | ( FORMAT_RGBA8 < < 8 ) :
_convert < 1 , true , 3 , true , true , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_R8 | ( FORMAT_L8 < < 8 ) :
_convert < 1 , false , 1 , false , false , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_R8 | ( FORMAT_LA8 < < 8 ) :
_convert < 1 , false , 1 , true , false , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_R8 | ( FORMAT_RG8 < < 8 ) :
_convert < 1 , false , 2 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_R8 | ( FORMAT_RGB8 < < 8 ) :
_convert < 1 , false , 3 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_R8 | ( FORMAT_RGBA8 < < 8 ) :
_convert < 1 , false , 3 , true , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RG8 | ( FORMAT_L8 < < 8 ) :
_convert < 2 , false , 1 , false , false , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RG8 | ( FORMAT_LA8 < < 8 ) :
_convert < 2 , false , 1 , true , false , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RG8 | ( FORMAT_R8 < < 8 ) :
_convert < 2 , false , 1 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RG8 | ( FORMAT_RGB8 < < 8 ) :
_convert < 2 , false , 3 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RG8 | ( FORMAT_RGBA8 < < 8 ) :
_convert < 2 , false , 3 , true , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGB8 | ( FORMAT_L8 < < 8 ) :
_convert < 3 , false , 1 , false , false , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGB8 | ( FORMAT_LA8 < < 8 ) :
_convert < 3 , false , 1 , true , false , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGB8 | ( FORMAT_R8 < < 8 ) :
_convert < 3 , false , 1 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGB8 | ( FORMAT_RG8 < < 8 ) :
_convert < 3 , false , 2 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGB8 | ( FORMAT_RGBA8 < < 8 ) :
_convert < 3 , false , 3 , true , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGBA8 | ( FORMAT_L8 < < 8 ) :
_convert < 3 , true , 1 , false , false , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGBA8 | ( FORMAT_LA8 < < 8 ) :
_convert < 3 , true , 1 , true , false , true > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGBA8 | ( FORMAT_R8 < < 8 ) :
_convert < 3 , true , 1 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGBA8 | ( FORMAT_RG8 < < 8 ) :
_convert < 3 , true , 2 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
case FORMAT_RGBA8 | ( FORMAT_RGB8 < < 8 ) :
_convert < 3 , true , 3 , false , false , false > ( mip_width , mip_height , rptr , wptr ) ;
break ;
}
2014-02-10 02:10:30 +01:00
}
2017-05-17 12:36:47 +02:00
_copy_internals_from ( new_img ) ;
2014-02-10 02:10:30 +01:00
}
Image : : Format Image : : get_format ( ) const {
return format ;
}
2015-10-01 21:25:36 +02:00
static double _bicubic_interp_kernel ( double x ) {
x = ABS ( x ) ;
double bc = 0 ;
2020-05-14 16:41:43 +02:00
if ( x < = 1 ) {
2015-10-01 21:25:36 +02:00
bc = ( 1.5 * x - 2.5 ) * x * x + 1 ;
2020-05-14 16:41:43 +02:00
} else if ( x < 2 ) {
2015-10-01 21:25:36 +02:00
bc = ( ( - 0.5 * x + 2.5 ) * x - 4 ) * x + 2 ;
2020-05-14 16:41:43 +02:00
}
2015-10-01 21:25:36 +02:00
return bc ;
}
2018-08-22 22:56:47 +02:00
template < int CC , class T >
2018-06-01 21:56:55 +02:00
static void _scale_cubic ( const uint8_t * __restrict p_src , uint8_t * __restrict p_dst , uint32_t p_src_width , uint32_t p_src_height , uint32_t p_dst_width , uint32_t p_dst_height ) {
2015-10-01 21:25:36 +02:00
// get source image size
int width = p_src_width ;
int height = p_src_height ;
double xfac = ( double ) width / p_dst_width ;
double yfac = ( double ) height / p_dst_height ;
2017-09-22 05:58:29 +02:00
// coordinates of source points and coefficients
2022-09-29 08:18:07 +02:00
double ox , oy , dx , dy ;
2015-10-01 21:25:36 +02:00
int ox1 , oy1 , ox2 , oy2 ;
// destination pixel values
// width and height decreased by 1
int ymax = height - 1 ;
int xmax = width - 1 ;
// temporary pointer
2016-10-03 21:33:42 +02:00
for ( uint32_t y = 0 ; y < p_dst_height ; y + + ) {
2015-10-01 21:25:36 +02:00
// Y coordinates
oy = ( double ) y * yfac - 0.5f ;
oy1 = ( int ) oy ;
dy = oy - ( double ) oy1 ;
2016-10-03 21:33:42 +02:00
for ( uint32_t x = 0 ; x < p_dst_width ; x + + ) {
2015-10-01 21:25:36 +02:00
// X coordinates
ox = ( double ) x * xfac - 0.5f ;
ox1 = ( int ) ox ;
dx = ox - ( double ) ox1 ;
// initial pixel value
2018-08-22 21:40:43 +02:00
T * __restrict dst = ( ( T * ) p_dst ) + ( y * p_dst_width + x ) * CC ;
2015-10-01 21:25:36 +02:00
double color [ CC ] ;
for ( int i = 0 ; i < CC ; i + + ) {
color [ i ] = 0 ;
}
for ( int n = - 1 ; n < 3 ; n + + ) {
2017-09-22 05:58:29 +02:00
// get Y coefficient
2022-09-29 08:18:07 +02:00
[[maybe_unused]] double k1 = _bicubic_interp_kernel ( dy - ( double ) n ) ;
2015-10-01 21:25:36 +02:00
oy2 = oy1 + n ;
2020-05-14 16:41:43 +02:00
if ( oy2 < 0 ) {
2015-10-01 21:25:36 +02:00
oy2 = 0 ;
2020-05-14 16:41:43 +02:00
}
if ( oy2 > ymax ) {
2015-10-01 21:25:36 +02:00
oy2 = ymax ;
2020-05-14 16:41:43 +02:00
}
2015-10-01 21:25:36 +02:00
for ( int m = - 1 ; m < 3 ; m + + ) {
2017-09-22 05:58:29 +02:00
// get X coefficient
2022-09-29 08:18:07 +02:00
[[maybe_unused]] double k2 = k1 * _bicubic_interp_kernel ( ( double ) m - dx ) ;
2015-10-01 21:25:36 +02:00
ox2 = ox1 + m ;
2020-05-14 16:41:43 +02:00
if ( ox2 < 0 ) {
2015-10-01 21:25:36 +02:00
ox2 = 0 ;
2020-05-14 16:41:43 +02:00
}
if ( ox2 > xmax ) {
2015-10-01 21:25:36 +02:00
ox2 = xmax ;
2020-05-14 16:41:43 +02:00
}
2015-10-01 21:25:36 +02:00
// get pixel of original image
2018-08-22 21:40:43 +02:00
const T * __restrict p = ( ( T * ) p_src ) + ( oy2 * p_src_width + ox2 ) * CC ;
2015-10-01 21:25:36 +02:00
for ( int i = 0 ; i < CC ; i + + ) {
2022-09-29 08:18:07 +02:00
if constexpr ( sizeof ( T ) = = 2 ) { //half float
2018-08-22 21:40:43 +02:00
color [ i ] = Math : : half_to_float ( p [ i ] ) ;
} else {
color [ i ] + = p [ i ] * k2 ;
}
2015-10-01 21:25:36 +02:00
}
}
}
for ( int i = 0 ; i < CC ; i + + ) {
2022-09-29 08:18:07 +02:00
if constexpr ( sizeof ( T ) = = 1 ) { //byte
2018-08-22 21:40:43 +02:00
dst [ i ] = CLAMP ( Math : : fast_ftoi ( color [ i ] ) , 0 , 255 ) ;
2022-09-29 08:18:07 +02:00
} else if constexpr ( sizeof ( T ) = = 2 ) { //half float
2018-08-22 21:40:43 +02:00
dst [ i ] = Math : : make_half_float ( color [ i ] ) ;
} else {
dst [ i ] = color [ i ] ;
}
2015-10-01 21:25:36 +02:00
}
}
}
}
2018-08-22 22:56:47 +02:00
template < int CC , class T >
2018-06-01 21:56:55 +02:00
static void _scale_bilinear ( const uint8_t * __restrict p_src , uint8_t * __restrict p_dst , uint32_t p_src_width , uint32_t p_src_height , uint32_t p_dst_width , uint32_t p_dst_height ) {
2014-02-10 02:10:30 +01:00
enum {
FRAC_BITS = 8 ,
FRAC_LEN = ( 1 < < FRAC_BITS ) ,
2020-06-17 10:26:57 +02:00
FRAC_HALF = ( FRAC_LEN > > 1 ) ,
2014-02-10 02:10:30 +01:00
FRAC_MASK = FRAC_LEN - 1
} ;
for ( uint32_t i = 0 ; i < p_dst_height ; i + + ) {
2020-06-17 10:26:57 +02:00
// Add 0.5 in order to interpolate based on pixel center
uint32_t src_yofs_up_fp = ( i + 0.5 ) * p_src_height * FRAC_LEN / p_dst_height ;
// Calculate nearest src pixel center above current, and truncate to get y index
uint32_t src_yofs_up = src_yofs_up_fp > = FRAC_HALF ? ( src_yofs_up_fp - FRAC_HALF ) > > FRAC_BITS : 0 ;
uint32_t src_yofs_down = ( src_yofs_up_fp + FRAC_HALF ) > > FRAC_BITS ;
2020-05-14 16:41:43 +02:00
if ( src_yofs_down > = p_src_height ) {
2014-02-10 02:10:30 +01:00
src_yofs_down = p_src_height - 1 ;
2020-05-14 16:41:43 +02:00
}
2020-06-17 10:26:57 +02:00
// Calculate distance to pixel center of src_yofs_up
uint32_t src_yofs_frac = src_yofs_up_fp & FRAC_MASK ;
src_yofs_frac = src_yofs_frac > = FRAC_HALF ? src_yofs_frac - FRAC_HALF : src_yofs_frac + FRAC_HALF ;
2014-02-10 02:10:30 +01:00
uint32_t y_ofs_up = src_yofs_up * p_src_width * CC ;
uint32_t y_ofs_down = src_yofs_down * p_src_width * CC ;
for ( uint32_t j = 0 ; j < p_dst_width ; j + + ) {
2020-06-17 10:26:57 +02:00
uint32_t src_xofs_left_fp = ( j + 0.5 ) * p_src_width * FRAC_LEN / p_dst_width ;
uint32_t src_xofs_left = src_xofs_left_fp > = FRAC_HALF ? ( src_xofs_left_fp - FRAC_HALF ) > > FRAC_BITS : 0 ;
uint32_t src_xofs_right = ( src_xofs_left_fp + FRAC_HALF ) > > FRAC_BITS ;
2020-05-14 16:41:43 +02:00
if ( src_xofs_right > = p_src_width ) {
2014-02-10 02:10:30 +01:00
src_xofs_right = p_src_width - 1 ;
2020-05-14 16:41:43 +02:00
}
2020-06-17 10:26:57 +02:00
uint32_t src_xofs_frac = src_xofs_left_fp & FRAC_MASK ;
src_xofs_frac = src_xofs_frac > = FRAC_HALF ? src_xofs_frac - FRAC_HALF : src_xofs_frac + FRAC_HALF ;
2014-02-10 02:10:30 +01:00
src_xofs_left * = CC ;
src_xofs_right * = CC ;
for ( uint32_t l = 0 ; l < CC ; l + + ) {
2022-09-29 08:18:07 +02:00
if constexpr ( sizeof ( T ) = = 1 ) { //uint8
2018-08-22 21:40:43 +02:00
uint32_t p00 = p_src [ y_ofs_up + src_xofs_left + l ] < < FRAC_BITS ;
uint32_t p10 = p_src [ y_ofs_up + src_xofs_right + l ] < < FRAC_BITS ;
uint32_t p01 = p_src [ y_ofs_down + src_xofs_left + l ] < < FRAC_BITS ;
uint32_t p11 = p_src [ y_ofs_down + src_xofs_right + l ] < < FRAC_BITS ;
uint32_t interp_up = p00 + ( ( ( p10 - p00 ) * src_xofs_frac ) > > FRAC_BITS ) ;
uint32_t interp_down = p01 + ( ( ( p11 - p01 ) * src_xofs_frac ) > > FRAC_BITS ) ;
uint32_t interp = interp_up + ( ( ( interp_down - interp_up ) * src_yofs_frac ) > > FRAC_BITS ) ;
interp > > = FRAC_BITS ;
2021-10-25 12:43:59 +02:00
p_dst [ i * p_dst_width * CC + j * CC + l ] = uint8_t ( interp ) ;
2022-09-29 08:18:07 +02:00
} else if constexpr ( sizeof ( T ) = = 2 ) { //half float
2018-08-22 21:40:43 +02:00
float xofs_frac = float ( src_xofs_frac ) / ( 1 < < FRAC_BITS ) ;
float yofs_frac = float ( src_yofs_frac ) / ( 1 < < FRAC_BITS ) ;
const T * src = ( ( const T * ) p_src ) ;
T * dst = ( ( T * ) p_dst ) ;
float p00 = Math : : half_to_float ( src [ y_ofs_up + src_xofs_left + l ] ) ;
float p10 = Math : : half_to_float ( src [ y_ofs_up + src_xofs_right + l ] ) ;
float p01 = Math : : half_to_float ( src [ y_ofs_down + src_xofs_left + l ] ) ;
float p11 = Math : : half_to_float ( src [ y_ofs_down + src_xofs_right + l ] ) ;
float interp_up = p00 + ( p10 - p00 ) * xofs_frac ;
float interp_down = p01 + ( p11 - p01 ) * xofs_frac ;
float interp = interp_up + ( ( interp_down - interp_up ) * yofs_frac ) ;
dst [ i * p_dst_width * CC + j * CC + l ] = Math : : make_half_float ( interp ) ;
2022-09-29 08:18:07 +02:00
} else if constexpr ( sizeof ( T ) = = 4 ) { //float
2018-08-22 21:40:43 +02:00
float xofs_frac = float ( src_xofs_frac ) / ( 1 < < FRAC_BITS ) ;
float yofs_frac = float ( src_yofs_frac ) / ( 1 < < FRAC_BITS ) ;
const T * src = ( ( const T * ) p_src ) ;
T * dst = ( ( T * ) p_dst ) ;
float p00 = src [ y_ofs_up + src_xofs_left + l ] ;
float p10 = src [ y_ofs_up + src_xofs_right + l ] ;
float p01 = src [ y_ofs_down + src_xofs_left + l ] ;
float p11 = src [ y_ofs_down + src_xofs_right + l ] ;
float interp_up = p00 + ( p10 - p00 ) * xofs_frac ;
float interp_down = p01 + ( p11 - p01 ) * xofs_frac ;
float interp = interp_up + ( ( interp_down - interp_up ) * yofs_frac ) ;
dst [ i * p_dst_width * CC + j * CC + l ] = interp ;
}
2014-02-10 02:10:30 +01:00
}
}
}
}
2018-08-22 22:56:47 +02:00
template < int CC , class T >
2018-06-01 21:56:55 +02:00
static void _scale_nearest ( const uint8_t * __restrict p_src , uint8_t * __restrict p_dst , uint32_t p_src_width , uint32_t p_src_height , uint32_t p_dst_width , uint32_t p_dst_height ) {
2014-02-10 02:10:30 +01:00
for ( uint32_t i = 0 ; i < p_dst_height ; i + + ) {
uint32_t src_yofs = i * p_src_height / p_dst_height ;
uint32_t y_ofs = src_yofs * p_src_width * CC ;
for ( uint32_t j = 0 ; j < p_dst_width ; j + + ) {
uint32_t src_xofs = j * p_src_width / p_dst_width ;
src_xofs * = CC ;
for ( uint32_t l = 0 ; l < CC ; l + + ) {
2018-08-22 21:40:43 +02:00
const T * src = ( ( const T * ) p_src ) ;
T * dst = ( ( T * ) p_dst ) ;
T p = src [ y_ofs + src_xofs + l ] ;
dst [ i * p_dst_width * CC + j * CC + l ] = p ;
2014-02-10 02:10:30 +01:00
}
}
}
}
2019-05-05 14:03:52 +02:00
# define LANCZOS_TYPE 3
static float _lanczos ( float p_x ) {
return Math : : abs ( p_x ) > = LANCZOS_TYPE ? 0 : Math : : sincn ( p_x ) * Math : : sincn ( p_x / LANCZOS_TYPE ) ;
}
template < int CC , class T >
static void _scale_lanczos ( const uint8_t * __restrict p_src , uint8_t * __restrict p_dst , uint32_t p_src_width , uint32_t p_src_height , uint32_t p_dst_width , uint32_t p_dst_height ) {
int32_t src_width = p_src_width ;
int32_t src_height = p_src_height ;
int32_t dst_height = p_dst_height ;
int32_t dst_width = p_dst_width ;
uint32_t buffer_size = src_height * dst_width * CC ;
float * buffer = memnew_arr ( float , buffer_size ) ; // Store the first pass in a buffer
{ // FIRST PASS (horizontal)
float x_scale = float ( src_width ) / float ( dst_width ) ;
float scale_factor = MAX ( x_scale , 1 ) ; // A larger kernel is required only when downscaling
int32_t half_kernel = LANCZOS_TYPE * scale_factor ;
2019-05-31 13:22:22 +02:00
float * kernel = memnew_arr ( float , half_kernel * 2 ) ;
2019-05-05 14:03:52 +02:00
for ( int32_t buffer_x = 0 ; buffer_x < dst_width ; buffer_x + + ) {
2019-08-15 01:35:23 +02:00
// The corresponding point on the source image
float src_x = ( buffer_x + 0.5f ) * x_scale ; // Offset by 0.5 so it uses the pixel's center
int32_t start_x = MAX ( 0 , int32_t ( src_x ) - half_kernel + 1 ) ;
int32_t end_x = MIN ( src_width - 1 , int32_t ( src_x ) + half_kernel ) ;
2019-05-05 14:03:52 +02:00
// Create the kernel used by all the pixels of the column
2020-05-14 16:41:43 +02:00
for ( int32_t target_x = start_x ; target_x < = end_x ; target_x + + ) {
2019-08-15 01:35:23 +02:00
kernel [ target_x - start_x ] = _lanczos ( ( target_x + 0.5f - src_x ) / scale_factor ) ;
2020-05-14 16:41:43 +02:00
}
2019-05-05 14:03:52 +02:00
for ( int32_t buffer_y = 0 ; buffer_y < src_height ; buffer_y + + ) {
float pixel [ CC ] = { 0 } ;
float weight = 0 ;
for ( int32_t target_x = start_x ; target_x < = end_x ; target_x + + ) {
float lanczos_val = kernel [ target_x - start_x ] ;
weight + = lanczos_val ;
const T * __restrict src_data = ( ( const T * ) p_src ) + ( buffer_y * src_width + target_x ) * CC ;
for ( uint32_t i = 0 ; i < CC ; i + + ) {
2022-09-29 08:18:07 +02:00
if constexpr ( sizeof ( T ) = = 2 ) { //half float
2019-05-05 14:03:52 +02:00
pixel [ i ] + = Math : : half_to_float ( src_data [ i ] ) * lanczos_val ;
2020-05-14 16:41:43 +02:00
} else {
2019-05-05 14:03:52 +02:00
pixel [ i ] + = src_data [ i ] * lanczos_val ;
2020-05-14 16:41:43 +02:00
}
2019-05-05 14:03:52 +02:00
}
}
float * dst_data = ( ( float * ) buffer ) + ( buffer_y * dst_width + buffer_x ) * CC ;
2020-05-14 16:41:43 +02:00
for ( uint32_t i = 0 ; i < CC ; i + + ) {
2019-05-05 14:03:52 +02:00
dst_data [ i ] = pixel [ i ] / weight ; // Normalize the sum of all the samples
2020-05-14 16:41:43 +02:00
}
2019-05-05 14:03:52 +02:00
}
}
memdelete_arr ( kernel ) ;
} // End of first pass
{ // SECOND PASS (vertical + result)
float y_scale = float ( src_height ) / float ( dst_height ) ;
float scale_factor = MAX ( y_scale , 1 ) ;
int32_t half_kernel = LANCZOS_TYPE * scale_factor ;
2019-05-31 13:22:22 +02:00
float * kernel = memnew_arr ( float , half_kernel * 2 ) ;
2019-05-05 14:03:52 +02:00
for ( int32_t dst_y = 0 ; dst_y < dst_height ; dst_y + + ) {
2019-08-15 01:35:23 +02:00
float buffer_y = ( dst_y + 0.5f ) * y_scale ;
int32_t start_y = MAX ( 0 , int32_t ( buffer_y ) - half_kernel + 1 ) ;
int32_t end_y = MIN ( src_height - 1 , int32_t ( buffer_y ) + half_kernel ) ;
2019-05-05 14:03:52 +02:00
2020-05-14 16:41:43 +02:00
for ( int32_t target_y = start_y ; target_y < = end_y ; target_y + + ) {
2019-08-15 01:35:23 +02:00
kernel [ target_y - start_y ] = _lanczos ( ( target_y + 0.5f - buffer_y ) / scale_factor ) ;
2020-05-14 16:41:43 +02:00
}
2019-05-05 14:03:52 +02:00
for ( int32_t dst_x = 0 ; dst_x < dst_width ; dst_x + + ) {
float pixel [ CC ] = { 0 } ;
float weight = 0 ;
for ( int32_t target_y = start_y ; target_y < = end_y ; target_y + + ) {
float lanczos_val = kernel [ target_y - start_y ] ;
weight + = lanczos_val ;
float * buffer_data = ( ( float * ) buffer ) + ( target_y * dst_width + dst_x ) * CC ;
2020-05-14 16:41:43 +02:00
for ( uint32_t i = 0 ; i < CC ; i + + ) {
2019-05-05 14:03:52 +02:00
pixel [ i ] + = buffer_data [ i ] * lanczos_val ;
2020-05-14 16:41:43 +02:00
}
2019-05-05 14:03:52 +02:00
}
T * dst_data = ( ( T * ) p_dst ) + ( dst_y * dst_width + dst_x ) * CC ;
for ( uint32_t i = 0 ; i < CC ; i + + ) {
pixel [ i ] / = weight ;
2022-09-29 08:18:07 +02:00
if constexpr ( sizeof ( T ) = = 1 ) { //byte
2019-05-05 14:03:52 +02:00
dst_data [ i ] = CLAMP ( Math : : fast_ftoi ( pixel [ i ] ) , 0 , 255 ) ;
2022-09-29 08:18:07 +02:00
} else if constexpr ( sizeof ( T ) = = 2 ) { //half float
2019-05-05 14:03:52 +02:00
dst_data [ i ] = Math : : make_half_float ( pixel [ i ] ) ;
2020-05-14 16:41:43 +02:00
} else { // float
2019-05-05 14:03:52 +02:00
dst_data [ i ] = pixel [ i ] ;
2020-05-14 16:41:43 +02:00
}
2019-05-05 14:03:52 +02:00
}
}
}
memdelete_arr ( kernel ) ;
} // End of second pass
memdelete_arr ( buffer ) ;
}
2018-06-01 21:56:55 +02:00
static void _overlay ( const uint8_t * __restrict p_src , uint8_t * __restrict p_dst , float p_alpha , uint32_t p_width , uint32_t p_height , uint32_t p_pixel_size ) {
2019-07-20 08:09:57 +02:00
uint16_t alpha = MIN ( ( uint16_t ) ( p_alpha * 256.0f ) , 256 ) ;
2018-06-01 21:53:35 +02:00
for ( uint32_t i = 0 ; i < p_width * p_height * p_pixel_size ; i + + ) {
p_dst [ i ] = ( p_dst [ i ] * ( 256 - alpha ) + p_src [ i ] * alpha ) > > 8 ;
}
}
2019-03-07 16:15:10 +01:00
bool Image : : is_size_po2 ( ) const {
return uint32_t ( width ) = = next_power_of_2 ( width ) & & uint32_t ( height ) = = next_power_of_2 ( height ) ;
}
2020-12-18 15:14:55 +01:00
void Image : : resize_to_po2 ( bool p_square , Interpolation p_interpolation ) {
2019-08-26 17:21:19 +02:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot resize in compressed or custom image formats. " ) ;
2014-02-10 02:10:30 +01:00
2017-08-17 23:35:55 +02:00
int w = next_power_of_2 ( width ) ;
int h = next_power_of_2 ( height ) ;
2020-04-18 12:29:01 +02:00
if ( p_square ) {
w = h = MAX ( w , h ) ;
}
2014-02-10 02:10:30 +01:00
if ( w = = width & & h = = height ) {
2020-05-14 16:41:43 +02:00
if ( ! p_square | | w = = h ) {
2014-02-10 02:10:30 +01:00
return ; //nothing to do
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
2020-12-18 15:14:55 +01:00
resize ( w , h , p_interpolation ) ;
2014-02-10 02:10:30 +01:00
}
void Image : : resize ( int p_width , int p_height , Interpolation p_interpolation ) {
2022-07-22 20:06:19 +02:00
ERR_FAIL_COND_MSG ( data . size ( ) = = 0 , " Cannot resize image before creating it, use set_data() first. " ) ;
2019-08-26 17:21:19 +02:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot resize in compressed or custom image formats. " ) ;
2014-02-10 02:10:30 +01:00
2018-06-01 21:53:35 +02:00
bool mipmap_aware = p_interpolation = = INTERPOLATE_TRILINEAR /* || p_interpolation == INTERPOLATE_TRICUBIC */ ;
2019-10-28 08:07:29 +01:00
ERR_FAIL_COND_MSG ( p_width < = 0 , " Image width must be greater than 0. " ) ;
ERR_FAIL_COND_MSG ( p_height < = 0 , " Image height must be greater than 0. " ) ;
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_MSG ( p_width > MAX_WIDTH , " Image width cannot be greater than " + itos ( MAX_WIDTH ) + " . " ) ;
ERR_FAIL_COND_MSG ( p_height > MAX_HEIGHT , " Image height cannot be greater than " + itos ( MAX_HEIGHT ) + " . " ) ;
2019-10-31 23:54:21 +01:00
ERR_FAIL_COND_MSG ( p_width * p_height > MAX_PIXELS , " Too many pixels for image, maximum is " + itos ( MAX_PIXELS ) ) ;
2014-02-10 02:10:30 +01:00
2020-05-14 16:41:43 +02:00
if ( p_width = = width & & p_height = = height ) {
2014-02-10 02:10:30 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2016-03-09 00:00:52 +01:00
2020-05-14 11:00:19 +02:00
Image dst ( p_width , p_height , false , format ) ;
2016-03-09 00:00:52 +01:00
2018-06-01 21:53:35 +02:00
// Setup mipmap-aware scaling
Image dst2 ;
2018-10-04 13:04:58 +02:00
int mip1 = 0 ;
int mip2 = 0 ;
float mip1_weight = 0 ;
2018-06-01 21:53:35 +02:00
if ( mipmap_aware ) {
float avg_scale = ( ( float ) p_width / width + ( float ) p_height / height ) * 0.5f ;
if ( avg_scale > = 1.0f ) {
mipmap_aware = false ;
} else {
float level = Math : : log ( 1.0f / avg_scale ) / Math : : log ( 2.0f ) ;
mip1 = CLAMP ( ( int ) Math : : floor ( level ) , 0 , get_mipmap_count ( ) ) ;
mip2 = CLAMP ( ( int ) Math : : ceil ( level ) , 0 , get_mipmap_count ( ) ) ;
mip1_weight = 1.0f - ( level - mip1 ) ;
}
}
bool interpolate_mipmaps = mipmap_aware & & mip1 ! = mip2 ;
if ( interpolate_mipmaps ) {
2022-07-22 20:06:19 +02:00
dst2 . initialize_data ( p_width , p_height , false , format ) ;
2018-06-01 21:53:35 +02:00
}
2018-10-03 17:27:22 +02:00
2018-06-01 21:53:35 +02:00
bool had_mipmaps = mipmaps ;
if ( interpolate_mipmaps & & ! had_mipmaps ) {
generate_mipmaps ( ) ;
}
// --
2020-02-17 22:06:54 +01:00
const uint8_t * r = data . ptr ( ) ;
const unsigned char * r_ptr = r ;
2014-02-10 02:10:30 +01:00
2020-02-17 22:06:54 +01:00
uint8_t * w = dst . data . ptrw ( ) ;
unsigned char * w_ptr = w ;
2014-02-10 02:10:30 +01:00
switch ( p_interpolation ) {
case INTERPOLATE_NEAREST : {
2018-08-22 21:40:43 +02:00
if ( format > = FORMAT_L8 & & format < = FORMAT_RGBA8 ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 1 :
_scale_nearest < 1 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 2 :
_scale_nearest < 2 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 3 :
_scale_nearest < 3 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 4 :
_scale_nearest < 4 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
} else if ( format > = FORMAT_RF & & format < = FORMAT_RGBAF ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 4 :
_scale_nearest < 1 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 8 :
_scale_nearest < 2 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 12 :
_scale_nearest < 3 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 16 :
_scale_nearest < 4 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
} else if ( format > = FORMAT_RH & & format < = FORMAT_RGBAH ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 2 :
_scale_nearest < 1 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 4 :
_scale_nearest < 2 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 6 :
_scale_nearest < 3 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 8 :
_scale_nearest < 4 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
2018-08-22 21:40:43 +02:00
2014-02-10 02:10:30 +01:00
} break ;
2018-06-01 21:53:35 +02:00
case INTERPOLATE_BILINEAR :
case INTERPOLATE_TRILINEAR : {
for ( int i = 0 ; i < 2 ; + + i ) {
int src_width ;
int src_height ;
const unsigned char * src_ptr ;
if ( ! mipmap_aware ) {
if ( i = = 0 ) {
// Standard behavior
src_width = width ;
src_height = height ;
src_ptr = r_ptr ;
} else {
// No need for a second iteration
break ;
}
} else {
if ( i = = 0 ) {
// Read from the first mipmap that will be interpolated
// (if both levels are the same, we will not interpolate, but at least we'll sample from the right level)
int offs ;
_get_mipmap_offset_and_size ( mip1 , offs , src_width , src_height ) ;
src_ptr = r_ptr + offs ;
} else if ( ! interpolate_mipmaps ) {
// No need generate a second image
break ;
} else {
// Switch to read from the second mipmap that will be interpolated
int offs ;
_get_mipmap_offset_and_size ( mip2 , offs , src_width , src_height ) ;
src_ptr = r_ptr + offs ;
// Switch to write to the second destination image
2020-02-17 22:06:54 +01:00
w = dst2 . data . ptrw ( ) ;
w_ptr = w ;
2018-06-01 21:53:35 +02:00
}
}
2014-02-10 02:10:30 +01:00
2018-08-22 21:40:43 +02:00
if ( format > = FORMAT_L8 & & format < = FORMAT_RGBA8 ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 1 :
_scale_bilinear < 1 , uint8_t > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 2 :
_scale_bilinear < 2 , uint8_t > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 3 :
_scale_bilinear < 3 , uint8_t > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 4 :
_scale_bilinear < 4 , uint8_t > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
} else if ( format > = FORMAT_RF & & format < = FORMAT_RGBAF ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 4 :
_scale_bilinear < 1 , float > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 8 :
_scale_bilinear < 2 , float > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 12 :
_scale_bilinear < 3 , float > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 16 :
_scale_bilinear < 4 , float > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
} else if ( format > = FORMAT_RH & & format < = FORMAT_RGBAH ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 2 :
_scale_bilinear < 1 , uint16_t > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 4 :
_scale_bilinear < 2 , uint16_t > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 6 :
_scale_bilinear < 3 , uint16_t > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
case 8 :
_scale_bilinear < 4 , uint16_t > ( src_ptr , w_ptr , src_width , src_height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
2018-06-01 21:53:35 +02:00
}
}
if ( interpolate_mipmaps ) {
// Switch to read again from the first scaled mipmap to overlay it over the second
2020-02-17 22:06:54 +01:00
r = dst . data . ptr ( ) ;
_overlay ( r , w , mip1_weight , p_width , p_height , get_format_pixel_size ( format ) ) ;
2014-02-10 02:10:30 +01:00
}
} break ;
2015-10-01 21:25:36 +02:00
case INTERPOLATE_CUBIC : {
2018-08-22 21:40:43 +02:00
if ( format > = FORMAT_L8 & & format < = FORMAT_RGBA8 ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 1 :
_scale_cubic < 1 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 2 :
_scale_cubic < 2 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 3 :
_scale_cubic < 3 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 4 :
_scale_cubic < 4 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
} else if ( format > = FORMAT_RF & & format < = FORMAT_RGBAF ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 4 :
_scale_cubic < 1 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 8 :
_scale_cubic < 2 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 12 :
_scale_cubic < 3 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 16 :
_scale_cubic < 4 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
} else if ( format > = FORMAT_RH & & format < = FORMAT_RGBAH ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 2 :
_scale_cubic < 1 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 4 :
_scale_cubic < 2 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 6 :
_scale_cubic < 3 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 8 :
_scale_cubic < 4 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2018-08-22 21:40:43 +02:00
}
2015-10-01 21:25:36 +02:00
}
} break ;
2019-05-05 14:03:52 +02:00
case INTERPOLATE_LANCZOS : {
if ( format > = FORMAT_L8 & & format < = FORMAT_RGBA8 ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 1 :
_scale_lanczos < 1 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 2 :
_scale_lanczos < 2 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 3 :
_scale_lanczos < 3 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 4 :
_scale_lanczos < 4 , uint8_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2019-05-05 14:03:52 +02:00
}
} else if ( format > = FORMAT_RF & & format < = FORMAT_RGBAF ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 4 :
_scale_lanczos < 1 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 8 :
_scale_lanczos < 2 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 12 :
_scale_lanczos < 3 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 16 :
_scale_lanczos < 4 , float > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2019-05-05 14:03:52 +02:00
}
} else if ( format > = FORMAT_RH & & format < = FORMAT_RGBAH ) {
switch ( get_format_pixel_size ( format ) ) {
2020-05-10 13:00:47 +02:00
case 2 :
_scale_lanczos < 1 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 4 :
_scale_lanczos < 2 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 6 :
_scale_lanczos < 3 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
case 8 :
_scale_lanczos < 4 , uint16_t > ( r_ptr , w_ptr , width , height , p_width , p_height ) ;
break ;
2019-05-05 14:03:52 +02:00
}
}
} break ;
2014-02-10 02:10:30 +01:00
}
2018-06-01 21:53:35 +02:00
if ( interpolate_mipmaps ) {
dst . _copy_internals_from ( dst2 ) ;
}
2020-05-14 16:41:43 +02:00
if ( had_mipmaps ) {
2014-02-10 02:10:30 +01:00
dst . generate_mipmaps ( ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2017-05-17 12:36:47 +02:00
_copy_internals_from ( dst ) ;
2014-02-10 02:10:30 +01:00
}
2017-11-18 04:42:14 +01:00
void Image : : crop_from_point ( int p_x , int p_y , int p_width , int p_height ) {
2019-08-26 17:21:19 +02:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot crop in compressed or custom image formats. " ) ;
2019-08-15 04:57:49 +02:00
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_MSG ( p_x < 0 , " Start x position cannot be smaller than 0. " ) ;
ERR_FAIL_COND_MSG ( p_y < 0 , " Start y position cannot be smaller than 0. " ) ;
ERR_FAIL_COND_MSG ( p_width < = 0 , " Width of image must be greater than 0. " ) ;
ERR_FAIL_COND_MSG ( p_height < = 0 , " Height of image must be greater than 0. " ) ;
ERR_FAIL_COND_MSG ( p_x + p_width > MAX_WIDTH , " End x position cannot be greater than " + itos ( MAX_WIDTH ) + " . " ) ;
ERR_FAIL_COND_MSG ( p_y + p_height > MAX_HEIGHT , " End y position cannot be greater than " + itos ( MAX_HEIGHT ) + " . " ) ;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
/* to save memory, cropping should be done in-place, however, since this function
2018-01-18 21:37:17 +01:00
will most likely either not be used much , or in critical areas , for now it won ' t , because
2014-02-10 02:10:30 +01:00
it ' s a waste of time . */
2020-05-14 16:41:43 +02:00
if ( p_width = = width & & p_height = = height & & p_x = = 0 & & p_y = = 0 ) {
2014-02-10 02:10:30 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2016-03-09 00:00:52 +01:00
2016-10-03 21:33:42 +02:00
uint8_t pdata [ 16 ] ; //largest is 16
uint32_t pixel_size = get_format_pixel_size ( format ) ;
2020-05-14 11:00:19 +02:00
Image dst ( p_width , p_height , false , format ) ;
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
{
2020-02-17 22:06:54 +01:00
const uint8_t * r = data . ptr ( ) ;
uint8_t * w = dst . data . ptrw ( ) ;
2016-03-09 00:00:52 +01:00
2017-11-18 04:42:14 +01:00
int m_h = p_y + p_height ;
int m_w = p_x + p_width ;
for ( int y = p_y ; y < m_h ; y + + ) {
for ( int x = p_x ; x < m_w ; x + + ) {
2016-10-03 21:33:42 +02:00
if ( ( x > = width | | y > = height ) ) {
2020-05-14 16:41:43 +02:00
for ( uint32_t i = 0 ; i < pixel_size ; i + + ) {
2016-10-03 21:33:42 +02:00
pdata [ i ] = 0 ;
2020-05-14 16:41:43 +02:00
}
2016-10-03 21:33:42 +02:00
} else {
2020-02-17 22:06:54 +01:00
_get_pixelb ( x , y , pixel_size , r , pdata ) ;
2016-10-03 21:33:42 +02:00
}
2020-02-17 22:06:54 +01:00
dst . _put_pixelb ( x - p_x , y - p_y , pixel_size , w , pdata ) ;
2016-10-03 21:33:42 +02:00
}
2014-02-10 02:10:30 +01:00
}
}
2016-03-09 00:00:52 +01:00
2020-05-14 16:41:43 +02:00
if ( has_mipmaps ( ) ) {
2014-02-10 02:10:30 +01:00
dst . generate_mipmaps ( ) ;
2020-05-14 16:41:43 +02:00
}
2017-05-17 12:36:47 +02:00
_copy_internals_from ( dst ) ;
2014-02-10 02:10:30 +01:00
}
2017-11-18 04:42:14 +01:00
void Image : : crop ( int p_width , int p_height ) {
crop_from_point ( 0 , 0 , p_width , p_height ) ;
}
2022-07-14 21:20:12 +02:00
void Image : : rotate_90 ( ClockDirection p_direction ) {
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot rotate in compressed or custom image formats. " ) ;
2022-08-11 18:45:50 +02:00
ERR_FAIL_COND_MSG ( width < = 0 , " The Image width specified ( " + itos ( width ) + " pixels) must be greater than 0 pixels. " ) ;
ERR_FAIL_COND_MSG ( height < = 0 , " The Image height specified ( " + itos ( height ) + " pixels) must be greater than 0 pixels. " ) ;
2022-07-14 21:20:12 +02:00
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
clear_mipmaps ( ) ;
}
2022-08-11 18:45:50 +02:00
// In-place 90 degrees rotation by following the permutation cycles.
2022-07-14 21:20:12 +02:00
{
2022-08-11 18:45:50 +02:00
// Explanation by example (clockwise):
//
// abc da
// def -> eb
// fc
//
// In memory:
// 012345 012345
// abcdef -> daebfc
//
// Permutation cycles:
// (0 --a--> 1 --b--> 3 --d--> 0)
// (2 --c--> 5 --f--> 4 --e--> 2)
//
// Applying cycles (backwards):
// 0->s s=a (store)
// 3->0 abcdef -> dbcdef
// 1->3 dbcdef -> dbcbef
// s->1 dbcbef -> dacbef
//
// 2->s s=c
// 4->2 dacbef -> daebef
// 5->4 daebef -> daebff
// s->5 daebff -> daebfc
const int w = width ;
const int h = height ;
const int size = w * h ;
uint8_t * data_ptr = data . ptrw ( ) ;
2022-07-14 21:20:12 +02:00
uint32_t pixel_size = get_format_pixel_size ( format ) ;
2022-08-11 18:45:50 +02:00
uint8_t single_pixel_buffer [ 16 ] ;
2022-07-14 21:20:12 +02:00
2022-08-11 18:45:50 +02:00
# define PREV_INDEX_IN_CYCLE(index) (p_direction == CLOCKWISE) ? ((h - 1 - (index % h)) * w + (index / h)) : ((index % h) * w + (w - 1 - (index / h)))
2022-07-14 21:20:12 +02:00
2022-08-11 18:45:50 +02:00
if ( w = = h ) { // Square case, 4-length cycles only (plus irrelevant thus skipped 1-length cycle in the middle for odd-sized squares).
for ( int y = 0 ; y < h / 2 ; y + + ) {
for ( int x = 0 ; x < ( w + 1 ) / 2 ; x + + ) {
int current = y * w + x ;
memcpy ( single_pixel_buffer , data_ptr + current * pixel_size , pixel_size ) ;
for ( int i = 0 ; i < 3 ; i + + ) {
int prev = PREV_INDEX_IN_CYCLE ( current ) ;
memcpy ( data_ptr + current * pixel_size , data_ptr + prev * pixel_size , pixel_size ) ;
current = prev ;
}
memcpy ( data_ptr + current * pixel_size , single_pixel_buffer , pixel_size ) ;
2022-07-14 21:20:12 +02:00
}
}
2022-08-11 18:45:50 +02:00
} else { // Rectangular case (w != h), kinda unpredictable cycles.
int permuted_pixels_count = 0 ;
for ( int i = 0 ; i < size ; i + + ) {
int prev = PREV_INDEX_IN_CYCLE ( i ) ;
if ( prev = = i ) {
// 1-length cycle, pixel remains at the same index.
permuted_pixels_count + + ;
continue ;
}
2022-07-14 21:20:12 +02:00
2022-08-11 18:45:50 +02:00
// Check whether we already processed this cycle.
// We iterate over it and if we'll find an index smaller than `i` then we already
// processed this cycle because we always start at the smallest index in the cycle.
// TODO: Improve this naive approach, can be done better.
while ( prev > i ) {
prev = PREV_INDEX_IN_CYCLE ( prev ) ;
}
if ( prev < i ) {
continue ;
2022-07-14 21:20:12 +02:00
}
2022-08-11 18:45:50 +02:00
// Save the in-cycle pixel with the smallest index (`i`).
memcpy ( single_pixel_buffer , data_ptr + i * pixel_size , pixel_size ) ;
2022-07-14 21:20:12 +02:00
2022-08-11 18:45:50 +02:00
// Overwrite pixels one by one by the preceding pixel in the cycle.
int current = i ;
prev = PREV_INDEX_IN_CYCLE ( current ) ;
while ( prev ! = i ) {
memcpy ( data_ptr + current * pixel_size , data_ptr + prev * pixel_size , pixel_size ) ;
permuted_pixels_count + + ;
2022-07-14 21:20:12 +02:00
2022-08-11 18:45:50 +02:00
current = prev ;
prev = PREV_INDEX_IN_CYCLE ( current ) ;
} ;
// Overwrite the remaining pixel in the cycle by the saved pixel with the smallest index.
memcpy ( data_ptr + current * pixel_size , single_pixel_buffer , pixel_size ) ;
permuted_pixels_count + + ;
if ( permuted_pixels_count = = size ) {
break ;
2022-07-14 21:20:12 +02:00
}
}
2022-08-11 18:45:50 +02:00
width = h ;
height = w ;
2022-07-14 21:20:12 +02:00
}
2022-08-11 18:45:50 +02:00
# undef PREV_INDEX_IN_CYCLE
2022-07-14 21:20:12 +02:00
}
2022-08-11 18:45:50 +02:00
if ( used_mipmaps ) {
2022-07-14 21:20:12 +02:00
generate_mipmaps ( ) ;
}
}
void Image : : rotate_180 ( ) {
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot rotate in compressed or custom image formats. " ) ;
2022-08-11 18:45:50 +02:00
ERR_FAIL_COND_MSG ( width < = 0 , " The Image width specified ( " + itos ( width ) + " pixels) must be greater than 0 pixels. " ) ;
ERR_FAIL_COND_MSG ( height < = 0 , " The Image height specified ( " + itos ( height ) + " pixels) must be greater than 0 pixels. " ) ;
2022-07-14 21:20:12 +02:00
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
clear_mipmaps ( ) ;
}
{
2022-08-11 18:45:50 +02:00
uint8_t * data_ptr = data . ptrw ( ) ;
2022-07-14 21:20:12 +02:00
uint32_t pixel_size = get_format_pixel_size ( format ) ;
2022-08-11 18:45:50 +02:00
uint8_t single_pixel_buffer [ 16 ] ;
2022-07-14 21:20:12 +02:00
2022-08-11 18:45:50 +02:00
uint8_t * from_begin_ptr = data_ptr ;
uint8_t * from_end_ptr = data_ptr + ( width * height - 1 ) * pixel_size ;
while ( from_begin_ptr < from_end_ptr ) {
memcpy ( single_pixel_buffer , from_begin_ptr , pixel_size ) ;
memcpy ( from_begin_ptr , from_end_ptr , pixel_size ) ;
memcpy ( from_end_ptr , single_pixel_buffer , pixel_size ) ;
from_begin_ptr + = pixel_size ;
from_end_ptr - = pixel_size ;
2022-07-14 21:20:12 +02:00
}
}
if ( used_mipmaps ) {
generate_mipmaps ( ) ;
}
}
2014-02-10 02:10:30 +01:00
void Image : : flip_y ( ) {
2019-08-26 17:21:19 +02:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot flip_y in compressed or custom image formats. " ) ;
2014-02-10 02:10:30 +01:00
2018-10-03 17:27:22 +02:00
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
2017-01-14 18:03:38 +01:00
clear_mipmaps ( ) ;
2018-10-03 17:27:22 +02:00
}
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
{
2020-02-17 22:06:54 +01:00
uint8_t * w = data . ptrw ( ) ;
2016-10-03 21:33:42 +02:00
uint8_t up [ 16 ] ;
uint8_t down [ 16 ] ;
uint32_t pixel_size = get_format_pixel_size ( format ) ;
2014-02-10 02:10:30 +01:00
2017-06-09 05:23:50 +02:00
for ( int y = 0 ; y < height / 2 ; y + + ) {
2016-10-03 21:33:42 +02:00
for ( int x = 0 ; x < width ; x + + ) {
2020-02-17 22:06:54 +01:00
_get_pixelb ( x , y , pixel_size , w , up ) ;
_get_pixelb ( x , height - y - 1 , pixel_size , w , down ) ;
2016-10-03 21:33:42 +02:00
2020-02-17 22:06:54 +01:00
_put_pixelb ( x , height - y - 1 , pixel_size , w , up ) ;
_put_pixelb ( x , y , pixel_size , w , down ) ;
2016-10-03 21:33:42 +02:00
}
2014-02-10 02:10:30 +01:00
}
}
2016-10-03 21:33:42 +02:00
2018-10-03 17:27:22 +02:00
if ( used_mipmaps ) {
2017-01-14 18:03:38 +01:00
generate_mipmaps ( ) ;
2018-10-03 17:27:22 +02:00
}
2014-02-10 02:10:30 +01:00
}
void Image : : flip_x ( ) {
2019-08-26 17:21:19 +02:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot flip_x in compressed or custom image formats. " ) ;
2014-02-10 02:10:30 +01:00
2018-10-03 17:27:22 +02:00
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
2017-01-14 18:03:38 +01:00
clear_mipmaps ( ) ;
2018-10-03 17:27:22 +02:00
}
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
{
2020-02-17 22:06:54 +01:00
uint8_t * w = data . ptrw ( ) ;
2016-10-03 21:33:42 +02:00
uint8_t up [ 16 ] ;
uint8_t down [ 16 ] ;
uint32_t pixel_size = get_format_pixel_size ( format ) ;
for ( int y = 0 ; y < height ; y + + ) {
2017-06-09 05:23:50 +02:00
for ( int x = 0 ; x < width / 2 ; x + + ) {
2020-02-17 22:06:54 +01:00
_get_pixelb ( x , y , pixel_size , w , up ) ;
_get_pixelb ( width - x - 1 , y , pixel_size , w , down ) ;
2016-10-03 21:33:42 +02:00
2020-02-17 22:06:54 +01:00
_put_pixelb ( width - x - 1 , y , pixel_size , w , up ) ;
_put_pixelb ( x , y , pixel_size , w , down ) ;
2016-10-03 21:33:42 +02:00
}
2014-02-10 02:10:30 +01:00
}
}
2018-10-03 17:27:22 +02:00
if ( used_mipmaps ) {
2017-01-14 18:03:38 +01:00
generate_mipmaps ( ) ;
2018-10-03 17:27:22 +02:00
}
2014-02-10 02:10:30 +01:00
}
2021-06-08 15:38:45 +02:00
/// Get mipmap size and offset.
2019-07-27 15:23:24 +02:00
int Image : : _get_dst_image_size ( int p_width , int p_height , Format p_format , int & r_mipmaps , int p_mipmaps , int * r_mm_width , int * r_mm_height ) {
2021-06-08 15:38:45 +02:00
// Data offset in mipmaps (including the original texture).
2014-02-10 02:10:30 +01:00
int size = 0 ;
2021-06-08 15:38:45 +02:00
2014-02-10 02:10:30 +01:00
int w = p_width ;
int h = p_height ;
2021-06-08 15:38:45 +02:00
// Current mipmap index in the loop below. p_mipmaps is the target mipmap index.
// In this function, mipmap 0 represents the first mipmap instead of the original texture.
2014-02-10 02:10:30 +01:00
int mm = 0 ;
int pixsize = get_format_pixel_size ( p_format ) ;
int pixshift = get_format_pixel_rshift ( p_format ) ;
2017-05-27 02:49:49 +02:00
int block = get_format_block_size ( p_format ) ;
2021-06-08 15:38:45 +02:00
// Technically, you can still compress up to 1 px no matter the format, so commenting this.
2018-08-06 19:56:06 +02:00
//int minw, minh;
//get_format_min_pixel_size(p_format, minw, minh);
int minw = 1 , minh = 1 ;
2014-02-10 02:10:30 +01:00
while ( true ) {
2017-05-27 02:49:49 +02:00
int bw = w % block ! = 0 ? w + ( block - w % block ) : w ;
int bh = h % block ! = 0 ? h + ( block - h % block ) : h ;
int s = bw * bh ;
2014-02-10 02:10:30 +01:00
s * = pixsize ;
s > > = pixshift ;
size + = s ;
if ( p_mipmaps > = 0 ) {
w = MAX ( minw , w > > 1 ) ;
h = MAX ( minh , h > > 1 ) ;
} else {
2020-05-14 16:41:43 +02:00
if ( w = = minw & & h = = minh ) {
2014-02-10 02:10:30 +01:00
break ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
w = MAX ( minw , w > > 1 ) ;
h = MAX ( minh , h > > 1 ) ;
}
2021-06-08 15:38:45 +02:00
// Set mipmap size.
if ( r_mm_width ) {
2022-01-19 05:29:39 +01:00
* r_mm_width = w ;
2021-06-08 15:38:45 +02:00
}
if ( r_mm_height ) {
2022-01-19 05:29:39 +01:00
* r_mm_height = h ;
2021-06-08 15:38:45 +02:00
}
// Reach target mipmap.
if ( p_mipmaps > = 0 & & mm = = p_mipmaps ) {
break ;
}
2014-02-10 02:10:30 +01:00
mm + + ;
2020-05-19 15:46:49 +02:00
}
2014-02-10 02:10:30 +01:00
r_mipmaps = mm ;
return size ;
}
bool Image : : _can_modify ( Format p_format ) const {
2017-05-27 02:49:49 +02:00
return p_format < = FORMAT_RGBE9995 ;
2014-02-10 02:10:30 +01:00
}
2018-08-22 04:56:04 +02:00
template < class Component , int CC , bool renormalize ,
void ( * average_func ) ( Component & , const Component & , const Component & , const Component & , const Component & ) ,
void ( * renormalize_func ) ( Component * ) >
static void _generate_po2_mipmap ( const Component * p_src , Component * p_dst , uint32_t p_width , uint32_t p_height ) {
2014-02-10 02:10:30 +01:00
//fast power of 2 mipmap generation
2022-03-08 15:10:48 +01:00
uint32_t dst_w = MAX ( p_width > > 1 , 1u ) ;
uint32_t dst_h = MAX ( p_height > > 1 , 1u ) ;
2018-08-25 22:39:43 +02:00
int right_step = ( p_width = = 1 ) ? 0 : CC ;
int down_step = ( p_height = = 1 ) ? 0 : ( p_width * CC ) ;
2014-02-10 02:10:30 +01:00
for ( uint32_t i = 0 ; i < dst_h ; i + + ) {
2018-08-25 22:39:43 +02:00
const Component * rup_ptr = & p_src [ i * 2 * down_step ] ;
const Component * rdown_ptr = rup_ptr + down_step ;
2018-08-22 04:56:04 +02:00
Component * dst_ptr = & p_dst [ i * dst_w * CC ] ;
2014-02-10 02:10:30 +01:00
uint32_t count = dst_w ;
2019-11-01 16:16:31 +01:00
while ( count ) {
count - - ;
2014-02-10 02:10:30 +01:00
for ( int j = 0 ; j < CC ; j + + ) {
2018-08-25 22:39:43 +02:00
average_func ( dst_ptr [ j ] , rup_ptr [ j ] , rup_ptr [ j + right_step ] , rdown_ptr [ j ] , rdown_ptr [ j + right_step ] ) ;
2014-02-10 02:10:30 +01:00
}
2018-04-30 02:51:37 +02:00
if ( renormalize ) {
2018-08-22 04:56:04 +02:00
renormalize_func ( dst_ptr ) ;
2018-04-30 02:51:37 +02:00
}
2014-02-10 02:10:30 +01:00
dst_ptr + = CC ;
2018-08-25 22:39:43 +02:00
rup_ptr + = right_step * 2 ;
rdown_ptr + = right_step * 2 ;
2014-02-10 02:10:30 +01:00
}
}
}
2016-05-04 17:36:51 +02:00
void Image : : shrink_x2 ( ) {
ERR_FAIL_COND ( data . size ( ) = = 0 ) ;
if ( mipmaps ) {
//just use the lower mipmap as base and copy all
2020-02-17 22:06:54 +01:00
Vector < uint8_t > new_img ;
2016-05-04 17:36:51 +02:00
int ofs = get_mipmap_offset ( 1 ) ;
int new_size = data . size ( ) - ofs ;
new_img . resize ( new_size ) ;
2019-06-22 14:52:51 +02:00
ERR_FAIL_COND ( new_img . size ( ) = = 0 ) ;
2016-05-04 17:36:51 +02:00
{
2020-02-17 22:06:54 +01:00
uint8_t * w = new_img . ptrw ( ) ;
const uint8_t * r = data . ptr ( ) ;
2016-05-04 17:36:51 +02:00
2021-04-27 16:19:21 +02:00
memcpy ( w , & r [ ofs ] , new_size ) ;
2016-05-04 17:36:51 +02:00
}
2017-09-30 22:21:10 +02:00
width = MAX ( width / 2 , 1 ) ;
height = MAX ( height / 2 , 1 ) ;
2016-05-04 17:36:51 +02:00
data = new_img ;
} else {
2020-02-17 22:06:54 +01:00
Vector < uint8_t > new_img ;
2016-05-04 17:36:51 +02:00
2016-10-03 21:33:42 +02:00
ERR_FAIL_COND ( ! _can_modify ( format ) ) ;
2016-05-04 17:36:51 +02:00
int ps = get_format_pixel_size ( format ) ;
new_img . resize ( ( width / 2 ) * ( height / 2 ) * ps ) ;
2019-06-22 14:52:51 +02:00
ERR_FAIL_COND ( new_img . size ( ) = = 0 ) ;
2019-11-01 16:16:31 +01:00
ERR_FAIL_COND ( data . size ( ) = = 0 ) ;
2016-05-04 17:36:51 +02:00
{
2020-02-17 22:06:54 +01:00
uint8_t * w = new_img . ptrw ( ) ;
const uint8_t * r = data . ptr ( ) ;
2016-05-04 17:36:51 +02:00
switch ( format ) {
2016-10-03 21:33:42 +02:00
case FORMAT_L8 :
2020-05-10 13:00:47 +02:00
case FORMAT_R8 :
_generate_po2_mipmap < uint8_t , 1 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r , w , width , height ) ;
break ;
case FORMAT_LA8 :
_generate_po2_mipmap < uint8_t , 2 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r , w , width , height ) ;
break ;
case FORMAT_RG8 :
_generate_po2_mipmap < uint8_t , 2 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r , w , width , height ) ;
break ;
case FORMAT_RGB8 :
_generate_po2_mipmap < uint8_t , 3 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r , w , width , height ) ;
break ;
case FORMAT_RGBA8 :
_generate_po2_mipmap < uint8_t , 4 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r , w , width , height ) ;
break ;
case FORMAT_RF :
_generate_po2_mipmap < float , 1 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( r ) , reinterpret_cast < float * > ( w ) , width , height ) ;
break ;
case FORMAT_RGF :
_generate_po2_mipmap < float , 2 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( r ) , reinterpret_cast < float * > ( w ) , width , height ) ;
break ;
case FORMAT_RGBF :
_generate_po2_mipmap < float , 3 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( r ) , reinterpret_cast < float * > ( w ) , width , height ) ;
break ;
case FORMAT_RGBAF :
_generate_po2_mipmap < float , 4 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( r ) , reinterpret_cast < float * > ( w ) , width , height ) ;
break ;
case FORMAT_RH :
_generate_po2_mipmap < uint16_t , 1 , false , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( r ) , reinterpret_cast < uint16_t * > ( w ) , width , height ) ;
break ;
case FORMAT_RGH :
_generate_po2_mipmap < uint16_t , 2 , false , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( r ) , reinterpret_cast < uint16_t * > ( w ) , width , height ) ;
break ;
case FORMAT_RGBH :
_generate_po2_mipmap < uint16_t , 3 , false , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( r ) , reinterpret_cast < uint16_t * > ( w ) , width , height ) ;
break ;
case FORMAT_RGBAH :
_generate_po2_mipmap < uint16_t , 4 , false , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( r ) , reinterpret_cast < uint16_t * > ( w ) , width , height ) ;
break ;
case FORMAT_RGBE9995 :
_generate_po2_mipmap < uint32_t , 1 , false , Image : : average_4_rgbe9995 , Image : : renormalize_rgbe9995 > ( reinterpret_cast < const uint32_t * > ( r ) , reinterpret_cast < uint32_t * > ( w ) , width , height ) ;
break ;
2019-04-09 17:08:36 +02:00
default : {
}
2016-05-04 17:36:51 +02:00
}
}
width / = 2 ;
height / = 2 ;
data = new_img ;
}
}
2018-07-03 15:55:50 +02:00
void Image : : normalize ( ) {
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
clear_mipmaps ( ) ;
}
for ( int y = 0 ; y < height ; y + + ) {
for ( int x = 0 ; x < width ; x + + ) {
Color c = get_pixel ( x , y ) ;
Vector3 v ( c . r * 2.0 - 1.0 , c . g * 2.0 - 1.0 , c . b * 2.0 - 1.0 ) ;
v . normalize ( ) ;
c . r = v . x * 0.5 + 0.5 ;
c . g = v . y * 0.5 + 0.5 ;
c . b = v . z * 0.5 + 0.5 ;
set_pixel ( x , y , c ) ;
}
}
if ( used_mipmaps ) {
generate_mipmaps ( true ) ;
}
}
2018-04-30 02:51:37 +02:00
Error Image : : generate_mipmaps ( bool p_renormalize ) {
2019-08-26 17:21:19 +02:00
ERR_FAIL_COND_V_MSG ( ! _can_modify ( format ) , ERR_UNAVAILABLE , " Cannot generate mipmaps in compressed or custom image formats. " ) ;
2014-02-10 02:10:30 +01:00
2020-03-01 04:30:43 +01:00
ERR_FAIL_COND_V_MSG ( format = = FORMAT_RGBA4444 , ERR_UNAVAILABLE , " Cannot generate mipmaps from RGBA4444 format. " ) ;
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_V_MSG ( width = = 0 | | height = = 0 , ERR_UNCONFIGURED , " Cannot generate mipmaps with width or height equal to 0. " ) ;
2017-02-06 04:38:39 +01:00
int mmcount ;
2016-10-03 21:33:42 +02:00
int size = _get_dst_image_size ( width , height , format , mmcount ) ;
2014-02-10 02:10:30 +01:00
data . resize ( size ) ;
2020-02-17 22:06:54 +01:00
uint8_t * wp = data . ptrw ( ) ;
2014-02-10 02:10:30 +01:00
2018-03-03 22:19:31 +01:00
int prev_ofs = 0 ;
int prev_h = height ;
int prev_w = width ;
2014-02-10 02:10:30 +01:00
2018-08-25 22:39:43 +02:00
for ( int i = 1 ; i < = mmcount ; i + + ) {
2018-03-03 22:19:31 +01:00
int ofs , w , h ;
_get_mipmap_offset_and_size ( i , ofs , w , h ) ;
2014-02-10 02:10:30 +01:00
2018-03-03 22:19:31 +01:00
switch ( format ) {
case FORMAT_L8 :
2020-05-10 13:00:47 +02:00
case FORMAT_R8 :
_generate_po2_mipmap < uint8_t , 1 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
break ;
2018-03-03 22:19:31 +01:00
case FORMAT_LA8 :
2020-05-10 13:00:47 +02:00
case FORMAT_RG8 :
_generate_po2_mipmap < uint8_t , 2 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
break ;
2018-04-30 02:51:37 +02:00
case FORMAT_RGB8 :
2020-05-14 16:41:43 +02:00
if ( p_renormalize ) {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint8_t , 3 , true , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
} else {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint8_t , 3 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
}
2018-04-30 02:51:37 +02:00
break ;
case FORMAT_RGBA8 :
2020-05-14 16:41:43 +02:00
if ( p_renormalize ) {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint8_t , 4 , true , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
} else {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint8_t , 4 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
}
2018-08-22 04:56:04 +02:00
break ;
case FORMAT_RF :
_generate_po2_mipmap < float , 1 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( & wp [ prev_ofs ] ) , reinterpret_cast < float * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
break ;
case FORMAT_RGF :
_generate_po2_mipmap < float , 2 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( & wp [ prev_ofs ] ) , reinterpret_cast < float * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
break ;
case FORMAT_RGBF :
2020-05-14 16:41:43 +02:00
if ( p_renormalize ) {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < float , 3 , true , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( & wp [ prev_ofs ] ) , reinterpret_cast < float * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
} else {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < float , 3 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( & wp [ prev_ofs ] ) , reinterpret_cast < float * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
}
2018-08-22 04:56:04 +02:00
break ;
case FORMAT_RGBAF :
2020-05-14 16:41:43 +02:00
if ( p_renormalize ) {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < float , 4 , true , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( & wp [ prev_ofs ] ) , reinterpret_cast < float * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
} else {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < float , 4 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( & wp [ prev_ofs ] ) , reinterpret_cast < float * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
}
2018-08-22 04:56:04 +02:00
break ;
case FORMAT_RH :
_generate_po2_mipmap < uint16_t , 1 , false , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( & wp [ prev_ofs ] ) , reinterpret_cast < uint16_t * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
break ;
case FORMAT_RGH :
_generate_po2_mipmap < uint16_t , 2 , false , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( & wp [ prev_ofs ] ) , reinterpret_cast < uint16_t * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
break ;
case FORMAT_RGBH :
2020-05-14 16:41:43 +02:00
if ( p_renormalize ) {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint16_t , 3 , true , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( & wp [ prev_ofs ] ) , reinterpret_cast < uint16_t * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
} else {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint16_t , 3 , false , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( & wp [ prev_ofs ] ) , reinterpret_cast < uint16_t * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
}
2018-08-22 04:56:04 +02:00
break ;
case FORMAT_RGBAH :
2020-05-14 16:41:43 +02:00
if ( p_renormalize ) {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint16_t , 4 , true , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( & wp [ prev_ofs ] ) , reinterpret_cast < uint16_t * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
} else {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint16_t , 4 , false , Image : : average_4_half , Image : : renormalize_half > ( reinterpret_cast < const uint16_t * > ( & wp [ prev_ofs ] ) , reinterpret_cast < uint16_t * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
}
2018-08-22 04:56:04 +02:00
break ;
case FORMAT_RGBE9995 :
2020-05-14 16:41:43 +02:00
if ( p_renormalize ) {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint32_t , 1 , true , Image : : average_4_rgbe9995 , Image : : renormalize_rgbe9995 > ( reinterpret_cast < const uint32_t * > ( & wp [ prev_ofs ] ) , reinterpret_cast < uint32_t * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
} else {
2018-08-22 04:56:04 +02:00
_generate_po2_mipmap < uint32_t , 1 , false , Image : : average_4_rgbe9995 , Image : : renormalize_rgbe9995 > ( reinterpret_cast < const uint32_t * > ( & wp [ prev_ofs ] ) , reinterpret_cast < uint32_t * > ( & wp [ ofs ] ) , prev_w , prev_h ) ;
2020-05-14 16:41:43 +02:00
}
2018-08-22 04:56:04 +02:00
2018-04-30 02:51:37 +02:00
break ;
2019-04-09 17:08:36 +02:00
default : {
}
2014-02-10 02:10:30 +01:00
}
2018-03-03 22:19:31 +01:00
prev_ofs = ofs ;
prev_w = w ;
prev_h = h ;
2014-02-10 02:10:30 +01:00
}
2017-02-06 04:38:39 +01:00
mipmaps = true ;
2014-02-10 02:10:30 +01:00
return OK ;
}
2020-01-27 00:09:40 +01:00
Error Image : : generate_mipmap_roughness ( RoughnessChannel p_roughness_channel , const Ref < Image > & p_normal_map ) {
2023-09-26 23:28:27 +02:00
LocalVector < double > normal_sat_vec ; //summed area table
2020-01-27 00:09:40 +01:00
int normal_w = 0 , normal_h = 0 ;
2020-12-23 10:34:26 +01:00
ERR_FAIL_COND_V_MSG ( p_normal_map . is_null ( ) | | p_normal_map - > is_empty ( ) , ERR_INVALID_PARAMETER , " Must provide a valid normal map for roughness mipmaps " ) ;
2020-01-27 00:09:40 +01:00
Ref < Image > nm = p_normal_map - > duplicate ( ) ;
if ( nm - > is_compressed ( ) ) {
nm - > decompress ( ) ;
}
normal_w = nm - > get_width ( ) ;
normal_h = nm - > get_height ( ) ;
normal_sat_vec . resize ( normal_w * normal_h * 3 ) ;
2023-09-26 23:28:27 +02:00
double * normal_sat = normal_sat_vec . ptr ( ) ;
2020-01-27 00:09:40 +01:00
//create summed area table
for ( int y = 0 ; y < normal_h ; y + + ) {
double line_sum [ 3 ] = { 0 , 0 , 0 } ;
for ( int x = 0 ; x < normal_w ; x + + ) {
double normal [ 3 ] ;
Color color = nm - > get_pixel ( x , y ) ;
normal [ 0 ] = color . r * 2.0 - 1.0 ;
normal [ 1 ] = color . g * 2.0 - 1.0 ;
normal [ 2 ] = Math : : sqrt ( MAX ( 0.0 , 1.0 - ( normal [ 0 ] * normal [ 0 ] + normal [ 1 ] * normal [ 1 ] ) ) ) ; //reconstruct if missing
line_sum [ 0 ] + = normal [ 0 ] ;
line_sum [ 1 ] + = normal [ 1 ] ;
line_sum [ 2 ] + = normal [ 2 ] ;
uint32_t ofs = ( y * normal_w + x ) * 3 ;
normal_sat [ ofs + 0 ] = line_sum [ 0 ] ;
normal_sat [ ofs + 1 ] = line_sum [ 1 ] ;
normal_sat [ ofs + 2 ] = line_sum [ 2 ] ;
if ( y > 0 ) {
uint32_t prev_ofs = ( ( y - 1 ) * normal_w + x ) * 3 ;
normal_sat [ ofs + 0 ] + = normal_sat [ prev_ofs + 0 ] ;
normal_sat [ ofs + 1 ] + = normal_sat [ prev_ofs + 1 ] ;
normal_sat [ ofs + 2 ] + = normal_sat [ prev_ofs + 2 ] ;
}
}
}
#if 0
{
Vector3 beg ( normal_sat_vec [ 0 ] , normal_sat_vec [ 1 ] , normal_sat_vec [ 2 ] ) ;
Vector3 end ( normal_sat_vec [ normal_sat_vec . size ( ) - 3 ] , normal_sat_vec [ normal_sat_vec . size ( ) - 2 ] , normal_sat_vec [ normal_sat_vec . size ( ) - 1 ] ) ;
Vector3 avg = ( end - beg ) / ( normal_w * normal_h ) ;
print_line ( " average: " + avg ) ;
}
# endif
int mmcount ;
_get_dst_image_size ( width , height , format , mmcount ) ;
2020-02-17 22:06:54 +01:00
uint8_t * base_ptr = data . ptrw ( ) ;
2020-01-27 00:09:40 +01:00
for ( int i = 1 ; i < = mmcount ; i + + ) {
int ofs , w , h ;
_get_mipmap_offset_and_size ( i , ofs , w , h ) ;
uint8_t * ptr = & base_ptr [ ofs ] ;
for ( int x = 0 ; x < w ; x + + ) {
for ( int y = 0 ; y < h ; y + + ) {
int from_x = x * normal_w / w ;
int from_y = y * normal_h / h ;
int to_x = ( x + 1 ) * normal_w / w ;
int to_y = ( y + 1 ) * normal_h / h ;
to_x = MIN ( to_x - 1 , normal_w ) ;
to_y = MIN ( to_y - 1 , normal_h ) ;
int size_x = ( to_x - from_x ) + 1 ;
int size_y = ( to_y - from_y ) + 1 ;
//summed area table version (much faster)
double avg [ 3 ] = { 0 , 0 , 0 } ;
if ( from_x > 0 & & from_y > 0 ) {
uint32_t tofs = ( ( from_y - 1 ) * normal_w + ( from_x - 1 ) ) * 3 ;
avg [ 0 ] + = normal_sat [ tofs + 0 ] ;
avg [ 1 ] + = normal_sat [ tofs + 1 ] ;
avg [ 2 ] + = normal_sat [ tofs + 2 ] ;
}
2023-09-26 23:28:27 +02:00
if ( from_y > 0 & & to_x > 0 ) {
2020-01-27 00:09:40 +01:00
uint32_t tofs = ( ( from_y - 1 ) * normal_w + to_x ) * 3 ;
avg [ 0 ] - = normal_sat [ tofs + 0 ] ;
avg [ 1 ] - = normal_sat [ tofs + 1 ] ;
avg [ 2 ] - = normal_sat [ tofs + 2 ] ;
}
2023-09-26 23:28:27 +02:00
if ( from_x > 0 & & to_y > 0 ) {
2020-01-27 00:09:40 +01:00
uint32_t tofs = ( to_y * normal_w + ( from_x - 1 ) ) * 3 ;
avg [ 0 ] - = normal_sat [ tofs + 0 ] ;
avg [ 1 ] - = normal_sat [ tofs + 1 ] ;
avg [ 2 ] - = normal_sat [ tofs + 2 ] ;
}
2023-09-26 23:28:27 +02:00
if ( to_y > 0 & & to_x > 0 ) {
uint32_t tofs = ( to_y * normal_w + to_x ) * 3 ;
avg [ 0 ] + = normal_sat [ tofs + 0 ] ;
avg [ 1 ] + = normal_sat [ tofs + 1 ] ;
avg [ 2 ] + = normal_sat [ tofs + 2 ] ;
}
2020-01-27 00:09:40 +01:00
double div = double ( size_x * size_y ) ;
Vector3 vec ( avg [ 0 ] / div , avg [ 1 ] / div , avg [ 2 ] / div ) ;
float r = vec . length ( ) ;
int pixel_ofs = y * w + x ;
Color c = _get_color_at_ofs ( ptr , pixel_ofs ) ;
Fix some -Wmaybe-uninitialized warnings
Namely:
```
modules/basis_universal/register_types.cpp: In function 'Ref<Image> basis_universal_unpacker(const Vector<unsigned char>&)':
modules/basis_universal/register_types.cpp:266:15: warning: 'imgfmt' may be used uninitialized in this function [-Wmaybe-uninitialized]
266 | image->create(info.m_width, info.m_height, info.m_total_levels > 1, imgfmt, gpudata);
| ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
modules/basis_universal/register_types.cpp:255:39: warning: 'format' may be used uninitialized in this function [-Wmaybe-uninitialized]
255 | bool ret = tr.transcode_image_level(ptr, size, 0, i, dst + ofs, level.m_total_blocks - i, format);
| ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
```
servers/visual_server.cpp: In member function 'Error VisualServer::_surface_set_data(Array, uint32_t, uint32_t*, uint32_t, Vector<unsigned char>&, int, Vector<unsigned char>&, int, AABB&, Vector<AABB>&)':
servers/visual_server.cpp:636:15: warning: 'iw' may be used uninitialized in this function [-Wmaybe-uninitialized]
636 | copymem(&iw[i * 2], &v, 2);
| ^
```
```
core/image.cpp: In member function 'Error Image::generate_mipmap_roughness(Image::RoughnessChannel, const Ref<Image>&)':
core/image.cpp:1683:11: warning: 'roughness' may be used uninitialized in this function [-Wmaybe-uninitialized]
1683 | float roughness;
| ^~~~~~~~~
```
2020-03-27 12:36:59 +01:00
float roughness = 0 ;
2020-01-27 00:09:40 +01:00
switch ( p_roughness_channel ) {
case ROUGHNESS_CHANNEL_R : {
roughness = c . r ;
} break ;
case ROUGHNESS_CHANNEL_G : {
roughness = c . g ;
} break ;
case ROUGHNESS_CHANNEL_B : {
roughness = c . b ;
} break ;
case ROUGHNESS_CHANNEL_L : {
2019-09-24 02:01:26 +02:00
roughness = c . get_v ( ) ;
2020-01-27 00:09:40 +01:00
} break ;
case ROUGHNESS_CHANNEL_A : {
roughness = c . a ;
} break ;
}
float variance = 0 ;
if ( r < 1.0f ) {
float r2 = r * r ;
float kappa = ( 3.0f * r - r * r2 ) / ( 1.0f - r2 ) ;
variance = 0.25f / kappa ;
}
float threshold = 0.4 ;
roughness = Math : : sqrt ( roughness * roughness + MIN ( 3.0f * variance , threshold * threshold ) ) ;
switch ( p_roughness_channel ) {
case ROUGHNESS_CHANNEL_R : {
c . r = roughness ;
} break ;
case ROUGHNESS_CHANNEL_G : {
c . g = roughness ;
} break ;
case ROUGHNESS_CHANNEL_B : {
c . b = roughness ;
} break ;
case ROUGHNESS_CHANNEL_L : {
c . r = roughness ;
c . g = roughness ;
c . b = roughness ;
} break ;
case ROUGHNESS_CHANNEL_A : {
c . a = roughness ;
} break ;
}
_set_color_at_ofs ( ptr , pixel_ofs , c ) ;
}
}
#if 0
{
int size = get_mipmap_byte_size ( i ) ;
print_line ( " size for mimpap " + itos ( i ) + " : " + itos ( size ) ) ;
2020-02-17 22:06:54 +01:00
Vector < uint8_t > imgdata ;
2020-01-27 00:09:40 +01:00
imgdata . resize ( size ) ;
2020-02-17 22:06:54 +01:00
uint8_t * wr = imgdata . ptrw ( ) ;
2021-04-27 16:19:21 +02:00
memcpy ( wr . ptr ( ) , ptr , size ) ;
2020-02-17 22:06:54 +01:00
wr = uint8_t * ( ) ;
2022-07-22 20:06:19 +02:00
Ref < Image > im = Image : : create_from_data ( w , h , false , format , imgdata ) ;
2020-01-27 00:09:40 +01:00
im - > save_png ( " res://mipmap_ " + itos ( i ) + " .png " ) ;
}
# endif
}
return OK ;
}
2014-02-10 02:10:30 +01:00
void Image : : clear_mipmaps ( ) {
2020-05-14 16:41:43 +02:00
if ( ! mipmaps ) {
2014-02-10 02:10:30 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2020-12-15 13:04:21 +01:00
if ( is_empty ( ) ) {
2014-02-10 02:10:30 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
int ofs , w , h ;
_get_mipmap_offset_and_size ( 1 , ofs , w , h ) ;
2016-10-03 21:33:42 +02:00
data . resize ( ofs ) ;
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
mipmaps = false ;
2014-02-10 02:10:30 +01:00
}
2020-12-15 13:04:21 +01:00
bool Image : : is_empty ( ) const {
2014-02-10 02:10:30 +01:00
return ( data . size ( ) = = 0 ) ;
}
2020-02-17 22:06:54 +01:00
Vector < uint8_t > Image : : get_data ( ) const {
2014-02-10 02:10:30 +01:00
return data ;
}
2022-07-22 20:06:19 +02:00
Ref < Image > Image : : create_empty ( int p_width , int p_height , bool p_use_mipmaps , Format p_format ) {
Ref < Image > image ;
image . instantiate ( ) ;
image - > initialize_data ( p_width , p_height , p_use_mipmaps , p_format ) ;
return image ;
}
Ref < Image > Image : : create_from_data ( int p_width , int p_height , bool p_use_mipmaps , Format p_format , const Vector < uint8_t > & p_data ) {
Ref < Image > image ;
image . instantiate ( ) ;
image - > initialize_data ( p_width , p_height , p_use_mipmaps , p_format , p_data ) ;
return image ;
}
void Image : : set_data ( int p_width , int p_height , bool p_use_mipmaps , Format p_format , const Vector < uint8_t > & p_data ) {
initialize_data ( p_width , p_height , p_use_mipmaps , p_format , p_data ) ;
}
void Image : : initialize_data ( int p_width , int p_height , bool p_use_mipmaps , Format p_format ) {
2022-04-11 01:11:00 +02:00
ERR_FAIL_COND_MSG ( p_width < = 0 , " The Image width specified ( " + itos ( p_width ) + " pixels) must be greater than 0 pixels. " ) ;
ERR_FAIL_COND_MSG ( p_height < = 0 , " The Image height specified ( " + itos ( p_height ) + " pixels) must be greater than 0 pixels. " ) ;
ERR_FAIL_COND_MSG ( p_width > MAX_WIDTH ,
" The Image width specified ( " + itos ( p_width ) + " pixels) cannot be greater than " + itos ( MAX_WIDTH ) + " pixels. " ) ;
ERR_FAIL_COND_MSG ( p_height > MAX_HEIGHT ,
" The Image height specified ( " + itos ( p_height ) + " pixels) cannot be greater than " + itos ( MAX_HEIGHT ) + " pixels. " ) ;
ERR_FAIL_COND_MSG ( p_width * p_height > MAX_PIXELS ,
" Too many pixels for Image. Maximum is " + itos ( MAX_WIDTH ) + " x " + itos ( MAX_HEIGHT ) + " = " + itos ( MAX_PIXELS ) + " pixels. " ) ;
ERR_FAIL_INDEX_MSG ( p_format , FORMAT_MAX , " The Image format specified ( " + itos ( p_format ) + " ) is out of range. See Image's Format enum. " ) ;
2018-05-05 10:46:51 +02:00
2016-03-09 00:00:52 +01:00
int mm = 0 ;
2014-02-10 02:10:30 +01:00
int size = _get_dst_image_size ( p_width , p_height , p_format , mm , p_use_mipmaps ? - 1 : 0 ) ;
data . resize ( size ) ;
2020-02-17 22:06:54 +01:00
2014-02-10 02:10:30 +01:00
{
2020-02-17 22:06:54 +01:00
uint8_t * w = data . ptrw ( ) ;
2021-04-27 16:19:21 +02:00
memset ( w , 0 , size ) ;
2014-02-10 02:10:30 +01:00
}
width = p_width ;
height = p_height ;
2016-10-03 21:33:42 +02:00
mipmaps = p_use_mipmaps ;
2014-02-10 02:10:30 +01:00
format = p_format ;
}
2022-07-22 20:06:19 +02:00
void Image : : initialize_data ( int p_width , int p_height , bool p_use_mipmaps , Format p_format , const Vector < uint8_t > & p_data ) {
2022-04-11 01:11:00 +02:00
ERR_FAIL_COND_MSG ( p_width < = 0 , " The Image width specified ( " + itos ( p_width ) + " pixels) must be greater than 0 pixels. " ) ;
ERR_FAIL_COND_MSG ( p_height < = 0 , " The Image height specified ( " + itos ( p_height ) + " pixels) must be greater than 0 pixels. " ) ;
ERR_FAIL_COND_MSG ( p_width > MAX_WIDTH ,
" The Image width specified ( " + itos ( p_width ) + " pixels) cannot be greater than " + itos ( MAX_WIDTH ) + " pixels. " ) ;
ERR_FAIL_COND_MSG ( p_height > MAX_HEIGHT ,
" The Image height specified ( " + itos ( p_height ) + " pixels) cannot be greater than " + itos ( MAX_HEIGHT ) + " pixels. " ) ;
ERR_FAIL_COND_MSG ( p_width * p_height > MAX_PIXELS ,
" Too many pixels for Image. Maximum is " + itos ( MAX_WIDTH ) + " x " + itos ( MAX_HEIGHT ) + " = " + itos ( MAX_PIXELS ) + " pixels . " ) ;
ERR_FAIL_INDEX_MSG ( p_format , FORMAT_MAX , " The Image format specified ( " + itos ( p_format ) + " ) is out of range. See Image's Format enum. " ) ;
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
int mm ;
2017-02-06 04:38:39 +01:00
int size = _get_dst_image_size ( p_width , p_height , p_format , mm , p_use_mipmaps ? - 1 : 0 ) ;
2014-02-10 02:10:30 +01:00
2022-04-11 01:11:00 +02:00
if ( unlikely ( p_data . size ( ) ! = size ) ) {
2023-01-25 12:17:11 +01:00
String description_mipmaps = get_format_name ( p_format ) + " " ;
2022-04-11 01:11:00 +02:00
if ( p_use_mipmaps ) {
const int num_mipmaps = get_image_required_mipmaps ( p_width , p_height , p_format ) ;
if ( num_mipmaps ! = 1 ) {
2023-01-25 12:17:11 +01:00
description_mipmaps + = vformat ( " with %d mipmaps " , num_mipmaps ) ;
2022-04-11 01:11:00 +02:00
} else {
2023-01-25 12:17:11 +01:00
description_mipmaps + = " with 1 mipmap " ;
2022-04-11 01:11:00 +02:00
}
} else {
2023-01-25 12:17:11 +01:00
description_mipmaps + = " without mipmaps " ;
2022-04-11 01:11:00 +02:00
}
const String description = vformat ( " %dx%dx%d (%s) " , p_width , p_height , get_format_pixel_size ( p_format ) , description_mipmaps ) ;
ERR_FAIL_MSG ( vformat ( " Expected Image data size of %s = %d bytes, got %d bytes instead. " , description , size , p_data . size ( ) ) ) ;
}
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
height = p_height ;
width = p_width ;
format = p_format ;
2016-03-09 00:00:52 +01:00
data = p_data ;
2020-02-17 22:06:54 +01:00
2016-10-03 21:33:42 +02:00
mipmaps = p_use_mipmaps ;
2014-02-10 02:10:30 +01:00
}
2022-07-22 20:06:19 +02:00
void Image : : initialize_data ( const char * * p_xpm ) {
2018-10-04 18:54:20 +02:00
int size_width = 0 ;
int size_height = 0 ;
2014-02-10 02:10:30 +01:00
int pixelchars = 0 ;
2016-10-03 21:33:42 +02:00
mipmaps = false ;
2014-02-10 02:10:30 +01:00
bool has_alpha = false ;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
enum Status {
READING_HEADER ,
READING_COLORS ,
READING_PIXELS ,
DONE
} ;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
Status status = READING_HEADER ;
int line = 0 ;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
HashMap < String , Color > colormap ;
2018-10-04 18:54:20 +02:00
int colormap_size = 0 ;
uint32_t pixel_size = 0 ;
2020-05-13 14:09:56 +02:00
uint8_t * data_write = nullptr ;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
while ( status ! = DONE ) {
const char * line_ptr = p_xpm [ line ] ;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
switch ( status ) {
case READING_HEADER : {
String line_str = line_ptr ;
line_str . replace ( " \t " , " " ) ;
2016-03-09 00:00:52 +01:00
2015-06-29 05:29:49 +02:00
size_width = line_str . get_slicec ( ' ' , 0 ) . to_int ( ) ;
size_height = line_str . get_slicec ( ' ' , 1 ) . to_int ( ) ;
colormap_size = line_str . get_slicec ( ' ' , 2 ) . to_int ( ) ;
pixelchars = line_str . get_slicec ( ' ' , 3 ) . to_int ( ) ;
2014-02-10 02:10:30 +01:00
ERR_FAIL_COND ( colormap_size > 32766 ) ;
ERR_FAIL_COND ( pixelchars > 5 ) ;
ERR_FAIL_COND ( size_width > 32767 ) ;
ERR_FAIL_COND ( size_height > 32767 ) ;
status = READING_COLORS ;
} break ;
case READING_COLORS : {
String colorstring ;
for ( int i = 0 ; i < pixelchars ; i + + ) {
colorstring + = * line_ptr ;
line_ptr + + ;
}
//skip spaces
while ( * line_ptr = = ' ' | | * line_ptr = = ' \t ' | | * line_ptr = = 0 ) {
2020-05-14 16:41:43 +02:00
if ( * line_ptr = = 0 ) {
2014-02-10 02:10:30 +01:00
break ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
line_ptr + + ;
}
if ( * line_ptr = = ' c ' ) {
line_ptr + + ;
while ( * line_ptr = = ' ' | | * line_ptr = = ' \t ' | | * line_ptr = = 0 ) {
2020-05-14 16:41:43 +02:00
if ( * line_ptr = = 0 ) {
2014-02-10 02:10:30 +01:00
break ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
line_ptr + + ;
}
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
if ( * line_ptr = = ' # ' ) {
line_ptr + + ;
2017-09-01 22:33:39 +02:00
uint8_t col_r = 0 ;
uint8_t col_g = 0 ;
uint8_t col_b = 0 ;
2017-01-14 12:26:56 +01:00
//uint8_t col_a=255;
2017-03-05 16:44:50 +01:00
2014-02-10 02:10:30 +01:00
for ( int i = 0 ; i < 6 ; i + + ) {
char v = line_ptr [ i ] ;
2017-03-05 16:44:50 +01:00
2022-02-04 09:32:20 +01:00
if ( is_digit ( v ) ) {
2017-03-05 16:44:50 +01:00
v - = ' 0 ' ;
2020-05-14 16:41:43 +02:00
} else if ( v > = ' A ' & & v < = ' F ' ) {
2017-03-05 16:44:50 +01:00
v = ( v - ' A ' ) + 10 ;
2020-05-14 16:41:43 +02:00
} else if ( v > = ' a ' & & v < = ' f ' ) {
2017-03-05 16:44:50 +01:00
v = ( v - ' a ' ) + 10 ;
2020-05-14 16:41:43 +02:00
} else {
2017-03-05 16:44:50 +01:00
break ;
2020-05-14 16:41:43 +02:00
}
2017-03-05 16:44:50 +01:00
2014-02-10 02:10:30 +01:00
switch ( i ) {
2020-05-10 13:00:47 +02:00
case 0 :
col_r = v < < 4 ;
break ;
case 1 :
col_r | = v ;
break ;
case 2 :
col_g = v < < 4 ;
break ;
case 3 :
col_g | = v ;
break ;
case 4 :
col_b = v < < 4 ;
break ;
case 5 :
col_b | = v ;
break ;
2020-05-19 15:46:49 +02:00
}
2017-03-05 16:44:50 +01:00
}
2014-02-10 02:10:30 +01:00
// magenta mask
if ( col_r = = 255 & & col_g = = 0 & & col_b = = 255 ) {
colormap [ colorstring ] = Color ( 0 , 0 , 0 , 0 ) ;
has_alpha = true ;
} else {
colormap [ colorstring ] = Color ( col_r / 255.0 , col_g / 255.0 , col_b / 255.0 , 1.0 ) ;
2017-03-05 16:44:50 +01:00
}
2014-02-10 02:10:30 +01:00
}
}
if ( line = = colormap_size ) {
status = READING_PIXELS ;
2022-07-22 20:06:19 +02:00
initialize_data ( size_width , size_height , false , has_alpha ? FORMAT_RGBA8 : FORMAT_RGB8 ) ;
2020-05-13 14:09:56 +02:00
data_write = data . ptrw ( ) ;
2016-10-03 21:33:42 +02:00
pixel_size = has_alpha ? 4 : 3 ;
2014-02-10 02:10:30 +01:00
}
} break ;
case READING_PIXELS : {
int y = line - colormap_size - 1 ;
for ( int x = 0 ; x < size_width ; x + + ) {
char pixelstr [ 6 ] = { 0 , 0 , 0 , 0 , 0 , 0 } ;
2020-05-14 16:41:43 +02:00
for ( int i = 0 ; i < pixelchars ; i + + ) {
2014-02-10 02:10:30 +01:00
pixelstr [ i ] = line_ptr [ x * pixelchars + i ] ;
2020-05-14 16:41:43 +02:00
}
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
Color * colorptr = colormap . getptr ( pixelstr ) ;
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL ( colorptr ) ;
2016-10-03 21:33:42 +02:00
uint8_t pixel [ 4 ] ;
for ( uint32_t i = 0 ; i < pixel_size ; i + + ) {
pixel [ i ] = CLAMP ( ( * colorptr ) [ i ] * 255 , 0 , 255 ) ;
2017-03-05 16:44:50 +01:00
}
2020-05-13 14:09:56 +02:00
_put_pixelb ( x , y , pixel_size , data_write , pixel ) ;
2016-10-03 21:33:42 +02:00
}
2016-03-09 00:00:52 +01:00
2020-05-14 16:41:43 +02:00
if ( y = = ( size_height - 1 ) ) {
2014-02-10 02:10:30 +01:00
status = DONE ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
} break ;
2019-04-09 17:08:36 +02:00
default : {
}
2014-02-10 02:10:30 +01:00
}
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
line + + ;
}
}
2017-07-08 17:12:18 +02:00
# define DETECT_ALPHA_MAX_THRESHOLD 254
# define DETECT_ALPHA_MIN_THRESHOLD 2
# define DETECT_ALPHA(m_value) \
{ \
uint8_t value = m_value ; \
if ( value < DETECT_ALPHA_MIN_THRESHOLD ) \
bit = true ; \
else if ( value < DETECT_ALPHA_MAX_THRESHOLD ) { \
detected = true ; \
break ; \
} \
2015-06-02 00:42:34 +02:00
}
# define DETECT_NON_ALPHA(m_value) \
2017-03-05 16:44:50 +01:00
{ \
2015-06-02 00:42:34 +02:00
uint8_t value = m_value ; \
if ( value > 0 ) { \
2014-02-10 02:10:30 +01:00
detected = true ; \
2017-03-05 16:44:50 +01:00
break ; \
} \
}
2015-06-02 00:42:34 +02:00
bool Image : : is_invisible ( ) const {
2016-10-03 21:33:42 +02:00
if ( format = = FORMAT_L8 | |
2020-05-14 16:41:43 +02:00
format = = FORMAT_RGB8 | | format = = FORMAT_RG8 ) {
2015-06-02 00:42:34 +02:00
return false ;
2020-05-14 16:41:43 +02:00
}
2015-06-02 00:42:34 +02:00
int len = data . size ( ) ;
2020-05-14 16:41:43 +02:00
if ( len = = 0 ) {
2015-06-02 00:42:34 +02:00
return true ;
2020-05-14 16:41:43 +02:00
}
2015-06-02 00:42:34 +02:00
int w , h ;
_get_mipmap_offset_and_size ( 1 , len , w , h ) ;
2020-02-17 22:06:54 +01:00
const uint8_t * r = data . ptr ( ) ;
const unsigned char * data_ptr = r ;
2015-06-02 00:42:34 +02:00
bool detected = false ;
switch ( format ) {
2016-10-03 21:33:42 +02:00
case FORMAT_LA8 : {
2015-06-02 00:42:34 +02:00
for ( int i = 0 ; i < ( len > > 1 ) ; i + + ) {
DETECT_NON_ALPHA ( data_ptr [ ( i < < 1 ) + 1 ] ) ;
}
} break ;
2016-10-03 21:33:42 +02:00
case FORMAT_RGBA8 : {
2015-06-02 00:42:34 +02:00
for ( int i = 0 ; i < ( len > > 2 ) ; i + + ) {
DETECT_NON_ALPHA ( data_ptr [ ( i < < 2 ) + 3 ] )
}
} break ;
2016-10-03 21:33:42 +02:00
case FORMAT_DXT3 :
case FORMAT_DXT5 : {
2015-06-02 00:42:34 +02:00
detected = true ;
} break ;
2019-04-09 17:08:36 +02:00
default : {
}
2015-06-02 00:42:34 +02:00
}
return ! detected ;
}
2014-02-10 02:10:30 +01:00
Image : : AlphaMode Image : : detect_alpha ( ) const {
int len = data . size ( ) ;
2020-05-14 16:41:43 +02:00
if ( len = = 0 ) {
2014-02-10 02:10:30 +01:00
return ALPHA_NONE ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
int w , h ;
_get_mipmap_offset_and_size ( 1 , len , w , h ) ;
2020-02-17 22:06:54 +01:00
const uint8_t * r = data . ptr ( ) ;
const unsigned char * data_ptr = r ;
2014-02-10 02:10:30 +01:00
bool bit = false ;
bool detected = false ;
switch ( format ) {
2016-10-03 21:33:42 +02:00
case FORMAT_LA8 : {
2014-02-10 02:10:30 +01:00
for ( int i = 0 ; i < ( len > > 1 ) ; i + + ) {
DETECT_ALPHA ( data_ptr [ ( i < < 1 ) + 1 ] ) ;
}
} break ;
2016-10-03 21:33:42 +02:00
case FORMAT_RGBA8 : {
2014-02-10 02:10:30 +01:00
for ( int i = 0 ; i < ( len > > 2 ) ; i + + ) {
DETECT_ALPHA ( data_ptr [ ( i < < 2 ) + 3 ] )
}
2016-10-03 21:33:42 +02:00
} break ;
case FORMAT_DXT3 :
case FORMAT_DXT5 : {
2014-02-10 02:10:30 +01:00
detected = true ;
} break ;
2019-04-09 17:08:36 +02:00
default : {
}
2014-02-10 02:10:30 +01:00
}
2020-05-14 16:41:43 +02:00
if ( detected ) {
2014-02-10 02:10:30 +01:00
return ALPHA_BLEND ;
2020-05-14 16:41:43 +02:00
} else if ( bit ) {
2014-02-10 02:10:30 +01:00
return ALPHA_BIT ;
2020-05-14 16:41:43 +02:00
} else {
2014-02-10 02:10:30 +01:00
return ALPHA_NONE ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
Error Image : : load ( const String & p_path ) {
2018-08-14 21:52:40 +02:00
# ifdef DEBUG_ENABLED
if ( p_path . begins_with ( " res:// " ) & & ResourceLoader : : exists ( p_path ) ) {
2019-11-07 09:44:15 +01:00
WARN_PRINT ( " Loaded resource as image file, this will not work on export: ' " + p_path + " '. Instead, import the image file as an Image resource and load it normally as a resource. " ) ;
2018-08-14 21:52:40 +02:00
}
# endif
2014-02-10 02:10:30 +01:00
return ImageLoader : : load_image ( p_path , this ) ;
}
2022-05-04 01:49:20 +02:00
Ref < Image > Image : : load_from_file ( const String & p_path ) {
# ifdef DEBUG_ENABLED
if ( p_path . begins_with ( " res:// " ) & & ResourceLoader : : exists ( p_path ) ) {
WARN_PRINT ( " Loaded resource as image file, this will not work on export: ' " + p_path + " '. Instead, import the image file as an Image resource and load it normally as a resource. " ) ;
}
# endif
Ref < Image > image ;
image . instantiate ( ) ;
Error err = ImageLoader : : load_image ( p_path , image ) ;
if ( err ! = OK ) {
ERR_FAIL_V_MSG ( Ref < Image > ( ) , vformat ( " Failed to load image. Error %d " , err ) ) ;
}
return image ;
}
2017-04-30 03:14:14 +02:00
Error Image : : save_png ( const String & p_path ) const {
2020-05-14 16:41:43 +02:00
if ( save_png_func = = nullptr ) {
2014-11-13 04:53:12 +01:00
return ERR_UNAVAILABLE ;
2020-05-14 16:41:43 +02:00
}
2014-11-13 04:53:12 +01:00
2017-06-09 05:23:50 +02:00
return save_png_func ( p_path , Ref < Image > ( ( Image * ) this ) ) ;
2014-02-10 02:10:30 +01:00
}
Implement Running Godot as Movie Writer
* Allows running the game in "movie writer" mode.
* It ensures entirely stable framerate, so your run can be saved stable and with proper sound (which is impossible if your CPU/GPU can't sustain doing this in real-time).
* If disabling vsync, it can save movies faster than the game is run, but if you want to control the interaction it can get difficult.
* Implements a simple, default MJPEG writer.
This new features has two main use cases, which have high demand:
* Saving game videos in high quality and ensuring the frame rate is *completely* stable, always.
* Using Godot as a tool to make movies and animations (which is ideal if you want interaction, or creating them procedurally. No other software is as good for this).
**Note**: This feature **IS NOT** for capturing real-time footage. Use something like OBS, SimpleScreenRecorder or FRAPS to achieve that, as they do a much better job at intercepting the compositor than Godot can probably do using Vulkan or OpenGL natively. If your game runs near real-time when capturing, you can still use this feature but it will play no sound (sound will be saved directly).
Usage:
$ godot --write-movie movie.avi [scene_file.tscn]
Missing:
* Options for configuring video writing via GLOBAL_DEF
* UI Menu for launching with this mode from the editor.
* Add to list of command line options.
* Add a feature tag to override configurations when movie writing (fantastic for saving videos with highest quality settings).
2022-06-17 00:55:19 +02:00
Error Image : : save_jpg ( const String & p_path , float p_quality ) const {
if ( save_jpg_func = = nullptr ) {
return ERR_UNAVAILABLE ;
}
return save_jpg_func ( p_path , Ref < Image > ( ( Image * ) this ) , p_quality ) ;
}
2020-02-17 22:06:54 +01:00
Vector < uint8_t > Image : : save_png_to_buffer ( ) const {
2020-04-02 01:20:12 +02:00
if ( save_png_buffer_func = = nullptr ) {
2020-02-17 22:06:54 +01:00
return Vector < uint8_t > ( ) ;
2019-10-31 23:54:21 +01:00
}
return save_png_buffer_func ( Ref < Image > ( ( Image * ) this ) ) ;
}
Implement Running Godot as Movie Writer
* Allows running the game in "movie writer" mode.
* It ensures entirely stable framerate, so your run can be saved stable and with proper sound (which is impossible if your CPU/GPU can't sustain doing this in real-time).
* If disabling vsync, it can save movies faster than the game is run, but if you want to control the interaction it can get difficult.
* Implements a simple, default MJPEG writer.
This new features has two main use cases, which have high demand:
* Saving game videos in high quality and ensuring the frame rate is *completely* stable, always.
* Using Godot as a tool to make movies and animations (which is ideal if you want interaction, or creating them procedurally. No other software is as good for this).
**Note**: This feature **IS NOT** for capturing real-time footage. Use something like OBS, SimpleScreenRecorder or FRAPS to achieve that, as they do a much better job at intercepting the compositor than Godot can probably do using Vulkan or OpenGL natively. If your game runs near real-time when capturing, you can still use this feature but it will play no sound (sound will be saved directly).
Usage:
$ godot --write-movie movie.avi [scene_file.tscn]
Missing:
* Options for configuring video writing via GLOBAL_DEF
* UI Menu for launching with this mode from the editor.
* Add to list of command line options.
* Add a feature tag to override configurations when movie writing (fantastic for saving videos with highest quality settings).
2022-06-17 00:55:19 +02:00
Vector < uint8_t > Image : : save_jpg_to_buffer ( float p_quality ) const {
if ( save_jpg_buffer_func = = nullptr ) {
return Vector < uint8_t > ( ) ;
}
return save_jpg_buffer_func ( Ref < Image > ( ( Image * ) this ) , p_quality ) ;
}
2019-08-03 02:07:13 +02:00
Error Image : : save_exr ( const String & p_path , bool p_grayscale ) const {
2020-05-14 16:41:43 +02:00
if ( save_exr_func = = nullptr ) {
2019-08-03 02:07:13 +02:00
return ERR_UNAVAILABLE ;
2020-05-14 16:41:43 +02:00
}
2019-08-03 02:07:13 +02:00
return save_exr_func ( p_path , Ref < Image > ( ( Image * ) this ) , p_grayscale ) ;
}
2022-06-24 05:55:37 +02:00
Vector < uint8_t > Image : : save_exr_to_buffer ( bool p_grayscale ) const {
2022-06-20 03:55:47 +02:00
if ( save_exr_buffer_func = = nullptr ) {
return Vector < uint8_t > ( ) ;
}
2022-06-24 05:55:37 +02:00
return save_exr_buffer_func ( Ref < Image > ( ( Image * ) this ) , p_grayscale ) ;
2022-06-20 03:55:47 +02:00
}
2022-06-04 23:05:55 +02:00
Error Image : : save_webp ( const String & p_path , const bool p_lossy , const float p_quality ) const {
if ( save_webp_func = = nullptr ) {
return ERR_UNAVAILABLE ;
}
ERR_FAIL_COND_V_MSG ( p_lossy & & ! ( 0.0f < = p_quality & & p_quality < = 1.0f ) , ERR_INVALID_PARAMETER , " The WebP lossy quality was set to " + rtos ( p_quality ) + " , which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive). " ) ;
return save_webp_func ( p_path , Ref < Image > ( ( Image * ) this ) , p_lossy , p_quality ) ;
}
Vector < uint8_t > Image : : save_webp_to_buffer ( const bool p_lossy , const float p_quality ) const {
if ( save_webp_buffer_func = = nullptr ) {
return Vector < uint8_t > ( ) ;
}
ERR_FAIL_COND_V_MSG ( p_lossy & & ! ( 0.0f < = p_quality & & p_quality < = 1.0f ) , Vector < uint8_t > ( ) , " The WebP lossy quality was set to " + rtos ( p_quality ) + " , which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive). " ) ;
return save_webp_buffer_func ( Ref < Image > ( ( Image * ) this ) , p_lossy , p_quality ) ;
}
2018-08-06 19:56:06 +02:00
int Image : : get_image_data_size ( int p_width , int p_height , Format p_format , bool p_mipmaps ) {
2014-02-10 02:10:30 +01:00
int mm ;
2018-08-06 19:56:06 +02:00
return _get_dst_image_size ( p_width , p_height , p_format , mm , p_mipmaps ? - 1 : 0 ) ;
2014-02-10 02:10:30 +01:00
}
int Image : : get_image_required_mipmaps ( int p_width , int p_height , Format p_format ) {
int mm ;
_get_dst_image_size ( p_width , p_height , p_format , mm , - 1 ) ;
return mm ;
}
2019-07-27 15:23:24 +02:00
Size2i Image : : get_image_mipmap_size ( int p_width , int p_height , Format p_format , int p_mipmap ) {
int mm ;
Size2i ret ;
_get_dst_image_size ( p_width , p_height , p_format , mm , p_mipmap , & ret . x , & ret . y ) ;
return ret ;
}
2018-11-16 23:12:00 +01:00
int Image : : get_image_mipmap_offset ( int p_width , int p_height , Format p_format , int p_mipmap ) {
if ( p_mipmap < = 0 ) {
return 0 ;
}
int mm ;
return _get_dst_image_size ( p_width , p_height , p_format , mm , p_mipmap - 1 ) ;
}
2019-09-27 04:16:44 +02:00
int Image : : get_image_mipmap_offset_and_dimensions ( int p_width , int p_height , Format p_format , int p_mipmap , int & r_w , int & r_h ) {
if ( p_mipmap < = 0 ) {
r_w = p_width ;
r_h = p_height ;
return 0 ;
}
int mm ;
return _get_dst_image_size ( p_width , p_height , p_format , mm , p_mipmap - 1 , & r_w , & r_h ) ;
}
2015-05-31 06:59:42 +02:00
bool Image : : is_compressed ( ) const {
2017-05-27 02:49:49 +02:00
return format > FORMAT_RGBE9995 ;
2015-05-31 06:59:42 +02:00
}
2014-06-11 15:41:03 +02:00
Error Image : : decompress ( ) {
2020-05-14 16:41:43 +02:00
if ( ( ( format > = FORMAT_DXT1 & & format < = FORMAT_RGTC_RG ) | | ( format = = FORMAT_DXT5_RA_AS_RG ) ) & & _image_decompress_bc ) {
2017-04-30 03:14:14 +02:00
_image_decompress_bc ( this ) ;
2020-05-14 16:41:43 +02:00
} else if ( format > = FORMAT_BPTC_RGBA & & format < = FORMAT_BPTC_RGBFU & & _image_decompress_bptc ) {
2018-08-22 04:56:04 +02:00
_image_decompress_bptc ( this ) ;
2020-05-14 16:41:43 +02:00
} else if ( format = = FORMAT_ETC & & _image_decompress_etc1 ) {
2017-04-26 17:49:08 +02:00
_image_decompress_etc1 ( this ) ;
2020-05-14 16:41:43 +02:00
} else if ( format > = FORMAT_ETC2_R11 & & format < = FORMAT_ETC2_RA_AS_RG & & _image_decompress_etc2 ) {
2016-10-03 21:33:42 +02:00
_image_decompress_etc2 ( this ) ;
2022-09-05 21:11:34 +02:00
} else if ( format > = FORMAT_ASTC_4x4 & & format < = FORMAT_ASTC_8x8_HDR & & _image_decompress_astc ) {
_image_decompress_astc ( this ) ;
2020-05-14 16:41:43 +02:00
} else {
2014-06-11 15:41:03 +02:00
return ERR_UNAVAILABLE ;
2020-05-14 16:41:43 +02:00
}
2014-06-11 15:41:03 +02:00
return OK ;
2014-02-10 02:10:30 +01:00
}
2023-01-25 12:17:11 +01:00
Error Image : : compress ( CompressMode p_mode , CompressSource p_source , ASTCFormat p_astc_format ) {
2021-06-25 13:50:22 +02:00
ERR_FAIL_INDEX_V_MSG ( p_mode , COMPRESS_MAX , ERR_INVALID_PARAMETER , " Invalid compress mode. " ) ;
ERR_FAIL_INDEX_V_MSG ( p_source , COMPRESS_SOURCE_MAX , ERR_INVALID_PARAMETER , " Invalid compress source. " ) ;
2023-01-25 12:17:11 +01:00
return compress_from_channels ( p_mode , detect_used_channels ( p_source ) , p_astc_format ) ;
2019-09-27 04:16:44 +02:00
}
2020-05-14 14:29:06 +02:00
2023-01-25 12:17:11 +01:00
Error Image : : compress_from_channels ( CompressMode p_mode , UsedChannels p_channels , ASTCFormat p_astc_format ) {
2022-12-17 20:04:20 +01:00
ERR_FAIL_COND_V ( data . is_empty ( ) , ERR_INVALID_DATA ) ;
2014-02-10 02:10:30 +01:00
switch ( p_mode ) {
2016-10-03 21:33:42 +02:00
case COMPRESS_S3TC : {
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL_V ( _image_compress_bc_func , ERR_UNAVAILABLE ) ;
2023-01-25 12:17:11 +01:00
_image_compress_bc_func ( this , p_channels ) ;
2014-02-10 02:10:30 +01:00
} break ;
case COMPRESS_ETC : {
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL_V ( _image_compress_etc1_func , ERR_UNAVAILABLE ) ;
2023-01-25 12:17:11 +01:00
_image_compress_etc1_func ( this ) ;
2016-10-03 21:33:42 +02:00
} break ;
case COMPRESS_ETC2 : {
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL_V ( _image_compress_etc2_func , ERR_UNAVAILABLE ) ;
2023-01-25 12:17:11 +01:00
_image_compress_etc2_func ( this , p_channels ) ;
2014-02-10 02:10:30 +01:00
} break ;
2018-08-22 04:56:04 +02:00
case COMPRESS_BPTC : {
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL_V ( _image_compress_bptc_func , ERR_UNAVAILABLE ) ;
2023-01-25 12:17:11 +01:00
_image_compress_bptc_func ( this , p_channels ) ;
2018-08-22 04:56:04 +02:00
} break ;
2022-09-05 21:11:34 +02:00
case COMPRESS_ASTC : {
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL_V ( _image_compress_astc_func , ERR_UNAVAILABLE ) ;
2023-01-25 12:17:11 +01:00
_image_compress_astc_func ( this , p_astc_format ) ;
2022-09-05 21:11:34 +02:00
} break ;
2021-06-25 13:50:22 +02:00
case COMPRESS_MAX : {
ERR_FAIL_V ( ERR_INVALID_PARAMETER ) ;
} break ;
2014-02-10 02:10:30 +01:00
}
return OK ;
}
Image : : Image ( const char * * p_xpm ) {
width = 0 ;
height = 0 ;
2016-10-03 21:33:42 +02:00
mipmaps = false ;
format = FORMAT_L8 ;
2014-02-10 02:10:30 +01:00
2022-07-22 20:06:19 +02:00
initialize_data ( p_xpm ) ;
2014-02-10 02:10:30 +01:00
}
Image : : Image ( int p_width , int p_height , bool p_use_mipmaps , Format p_format ) {
width = 0 ;
height = 0 ;
2016-10-03 21:33:42 +02:00
mipmaps = p_use_mipmaps ;
format = FORMAT_L8 ;
2014-02-10 02:10:30 +01:00
2022-07-22 20:06:19 +02:00
initialize_data ( p_width , p_height , p_use_mipmaps , p_format ) ;
2014-02-10 02:10:30 +01:00
}
2020-02-17 22:06:54 +01:00
Image : : Image ( int p_width , int p_height , bool p_mipmaps , Format p_format , const Vector < uint8_t > & p_data ) {
2014-02-10 02:10:30 +01:00
width = 0 ;
height = 0 ;
mipmaps = p_mipmaps ;
format = FORMAT_L8 ;
2022-07-22 20:06:19 +02:00
initialize_data ( p_width , p_height , p_mipmaps , p_format , p_data ) ;
2014-02-10 02:10:30 +01:00
}
2022-07-09 22:43:34 +02:00
Rect2i Image : : get_used_rect ( ) const {
2020-05-14 16:41:43 +02:00
if ( format ! = FORMAT_LA8 & & format ! = FORMAT_RGBA8 & & format ! = FORMAT_RGBAF & & format ! = FORMAT_RGBAH & & format ! = FORMAT_RGBA4444 & & format ! = FORMAT_RGB565 ) {
2022-07-09 22:43:34 +02:00
return Rect2i ( 0 , 0 , width , height ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
int len = data . size ( ) ;
2020-05-14 16:41:43 +02:00
if ( len = = 0 ) {
2022-07-09 22:43:34 +02:00
return Rect2i ( ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
int minx = 0xFFFFFF , miny = 0xFFFFFFF ;
int maxx = - 1 , maxy = - 1 ;
2016-10-03 21:33:42 +02:00
for ( int j = 0 ; j < height ; j + + ) {
for ( int i = 0 ; i < width ; i + + ) {
2020-05-14 16:41:43 +02:00
if ( ! ( get_pixel ( i , j ) . a > 0 ) ) {
2014-02-10 02:10:30 +01:00
continue ;
2020-05-14 16:41:43 +02:00
}
if ( i > maxx ) {
2014-02-10 02:10:30 +01:00
maxx = i ;
2020-05-14 16:41:43 +02:00
}
if ( j > maxy ) {
2014-02-10 02:10:30 +01:00
maxy = j ;
2020-05-14 16:41:43 +02:00
}
if ( i < minx ) {
2014-02-10 02:10:30 +01:00
minx = i ;
2020-05-14 16:41:43 +02:00
}
if ( j < miny ) {
2014-02-10 02:10:30 +01:00
miny = j ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
}
2020-05-14 16:41:43 +02:00
if ( maxx = = - 1 ) {
2022-07-09 22:43:34 +02:00
return Rect2i ( ) ;
2020-05-14 16:41:43 +02:00
} else {
2022-07-09 22:43:34 +02:00
return Rect2i ( minx , miny , maxx - minx + 1 , maxy - miny + 1 ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
2022-09-18 02:19:55 +02:00
Ref < Image > Image : : get_region ( const Rect2i & p_region ) const {
Ref < Image > img = memnew ( Image ( p_region . size . x , p_region . size . y , mipmaps , format ) ) ;
img - > blit_rect ( Ref < Image > ( ( Image * ) this ) , p_region , Point2i ( 0 , 0 ) ) ;
2014-02-10 02:10:30 +01:00
return img ;
}
2021-07-05 10:48:25 +02:00
void Image : : _get_clipped_src_and_dest_rects ( const Ref < Image > & p_src , const Rect2i & p_src_rect , const Point2i & p_dest , Rect2i & r_clipped_src_rect , Rect2i & r_clipped_dest_rect ) const {
r_clipped_dest_rect . position = p_dest ;
r_clipped_src_rect = p_src_rect ;
if ( r_clipped_src_rect . position . x < 0 ) {
r_clipped_dest_rect . position . x - = r_clipped_src_rect . position . x ;
r_clipped_src_rect . size . x + = r_clipped_src_rect . position . x ;
r_clipped_src_rect . position . x = 0 ;
}
if ( r_clipped_src_rect . position . y < 0 ) {
r_clipped_dest_rect . position . y - = r_clipped_src_rect . position . y ;
r_clipped_src_rect . size . y + = r_clipped_src_rect . position . y ;
r_clipped_src_rect . position . y = 0 ;
}
if ( r_clipped_dest_rect . position . x < 0 ) {
r_clipped_src_rect . position . x - = r_clipped_dest_rect . position . x ;
r_clipped_src_rect . size . x + = r_clipped_dest_rect . position . x ;
r_clipped_dest_rect . position . x = 0 ;
}
if ( r_clipped_dest_rect . position . y < 0 ) {
r_clipped_src_rect . position . y - = r_clipped_dest_rect . position . y ;
r_clipped_src_rect . size . y + = r_clipped_dest_rect . position . y ;
r_clipped_dest_rect . position . y = 0 ;
}
r_clipped_src_rect . size . x = MAX ( 0 , MIN ( r_clipped_src_rect . size . x , MIN ( p_src - > width - r_clipped_src_rect . position . x , width - r_clipped_dest_rect . position . x ) ) ) ;
r_clipped_src_rect . size . y = MAX ( 0 , MIN ( r_clipped_src_rect . size . y , MIN ( p_src - > height - r_clipped_src_rect . position . y , height - r_clipped_dest_rect . position . y ) ) ) ;
r_clipped_dest_rect . size . x = r_clipped_src_rect . size . x ;
r_clipped_dest_rect . size . y = r_clipped_src_rect . size . y ;
}
2022-07-09 22:43:34 +02:00
void Image : : blit_rect ( const Ref < Image > & p_src , const Rect2i & p_src_rect , const Point2i & p_dest ) {
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_MSG ( p_src . is_null ( ) , " It's not a reference to a valid Image object. " ) ;
2014-02-10 02:10:30 +01:00
int dsize = data . size ( ) ;
2017-05-17 12:36:47 +02:00
int srcdsize = p_src - > data . size ( ) ;
2014-02-10 02:10:30 +01:00
ERR_FAIL_COND ( dsize = = 0 ) ;
ERR_FAIL_COND ( srcdsize = = 0 ) ;
2017-05-17 12:36:47 +02:00
ERR_FAIL_COND ( format ! = p_src - > format ) ;
2020-01-28 05:35:31 +01:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot blit_rect in compressed or custom image formats. " ) ;
2014-02-10 02:10:30 +01:00
2021-07-05 10:48:25 +02:00
Rect2i src_rect ;
Rect2i dest_rect ;
_get_clipped_src_and_dest_rects ( p_src , p_src_rect , p_dest , src_rect , dest_rect ) ;
2022-08-15 04:51:45 +02:00
if ( ! src_rect . has_area ( ) | | ! dest_rect . has_area ( ) ) {
2016-10-03 21:33:42 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2017-06-14 15:56:58 +02:00
2020-02-17 22:06:54 +01:00
uint8_t * wp = data . ptrw ( ) ;
uint8_t * dst_data_ptr = wp ;
2014-02-10 02:10:30 +01:00
2020-02-17 22:06:54 +01:00
const uint8_t * rp = p_src - > data . ptr ( ) ;
const uint8_t * src_data_ptr = rp ;
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
int pixel_size = get_format_pixel_size ( format ) ;
2014-02-10 02:10:30 +01:00
2017-06-14 15:56:58 +02:00
for ( int i = 0 ; i < dest_rect . size . y ; i + + ) {
for ( int j = 0 ; j < dest_rect . size . x ; j + + ) {
2021-07-05 10:48:25 +02:00
int src_x = src_rect . position . x + j ;
int src_y = src_rect . position . y + i ;
2014-02-10 02:10:30 +01:00
2017-06-14 15:56:58 +02:00
int dst_x = dest_rect . position . x + j ;
int dst_y = dest_rect . position . y + i ;
2014-02-10 02:10:30 +01:00
2017-05-17 12:36:47 +02:00
const uint8_t * src = & src_data_ptr [ ( src_y * p_src - > width + src_x ) * pixel_size ] ;
2016-10-03 21:33:42 +02:00
uint8_t * dst = & dst_data_ptr [ ( dst_y * width + dst_x ) * pixel_size ] ;
2014-02-10 02:10:30 +01:00
2016-10-03 21:33:42 +02:00
for ( int k = 0 ; k < pixel_size ; k + + ) {
dst [ k ] = src [ k ] ;
2014-02-10 02:10:30 +01:00
}
}
}
}
2022-07-09 22:43:34 +02:00
void Image : : blit_rect_mask ( const Ref < Image > & p_src , const Ref < Image > & p_mask , const Rect2i & p_src_rect , const Point2i & p_dest ) {
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_MSG ( p_src . is_null ( ) , " It's not a reference to a valid Image object. " ) ;
ERR_FAIL_COND_MSG ( p_mask . is_null ( ) , " It's not a reference to a valid Image object. " ) ;
2017-06-24 17:53:54 +02:00
int dsize = data . size ( ) ;
int srcdsize = p_src - > data . size ( ) ;
int maskdsize = p_mask - > data . size ( ) ;
ERR_FAIL_COND ( dsize = = 0 ) ;
ERR_FAIL_COND ( srcdsize = = 0 ) ;
ERR_FAIL_COND ( maskdsize = = 0 ) ;
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_MSG ( p_src - > width ! = p_mask - > width , " Source image width is different from mask width. " ) ;
ERR_FAIL_COND_MSG ( p_src - > height ! = p_mask - > height , " Source image height is different from mask height. " ) ;
2017-06-24 17:53:54 +02:00
ERR_FAIL_COND ( format ! = p_src - > format ) ;
2021-07-05 10:48:25 +02:00
Rect2i src_rect ;
Rect2i dest_rect ;
_get_clipped_src_and_dest_rects ( p_src , p_src_rect , p_dest , src_rect , dest_rect ) ;
2022-08-15 04:51:45 +02:00
if ( ! src_rect . has_area ( ) | | ! dest_rect . has_area ( ) ) {
2017-06-24 17:53:54 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2017-06-24 17:53:54 +02:00
2020-02-17 22:06:54 +01:00
uint8_t * wp = data . ptrw ( ) ;
uint8_t * dst_data_ptr = wp ;
2017-06-24 17:53:54 +02:00
2020-02-17 22:06:54 +01:00
const uint8_t * rp = p_src - > data . ptr ( ) ;
const uint8_t * src_data_ptr = rp ;
2017-06-24 17:53:54 +02:00
int pixel_size = get_format_pixel_size ( format ) ;
2017-07-08 17:12:18 +02:00
2017-06-24 17:53:54 +02:00
Ref < Image > msk = p_mask ;
for ( int i = 0 ; i < dest_rect . size . y ; i + + ) {
for ( int j = 0 ; j < dest_rect . size . x ; j + + ) {
2021-07-05 10:48:25 +02:00
int src_x = src_rect . position . x + j ;
int src_y = src_rect . position . y + i ;
2017-07-08 17:12:18 +02:00
2017-06-24 17:53:54 +02:00
if ( msk - > get_pixel ( src_x , src_y ) . a ! = 0 ) {
int dst_x = dest_rect . position . x + j ;
int dst_y = dest_rect . position . y + i ;
const uint8_t * src = & src_data_ptr [ ( src_y * p_src - > width + src_x ) * pixel_size ] ;
uint8_t * dst = & dst_data_ptr [ ( dst_y * width + dst_x ) * pixel_size ] ;
for ( int k = 0 ; k < pixel_size ; k + + ) {
dst [ k ] = src [ k ] ;
}
}
}
}
}
2022-07-09 22:43:34 +02:00
void Image : : blend_rect ( const Ref < Image > & p_src , const Rect2i & p_src_rect , const Point2i & p_dest ) {
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_MSG ( p_src . is_null ( ) , " It's not a reference to a valid Image object. " ) ;
2017-06-18 18:38:43 +02:00
int dsize = data . size ( ) ;
int srcdsize = p_src - > data . size ( ) ;
ERR_FAIL_COND ( dsize = = 0 ) ;
ERR_FAIL_COND ( srcdsize = = 0 ) ;
ERR_FAIL_COND ( format ! = p_src - > format ) ;
2021-07-05 10:48:25 +02:00
Rect2i src_rect ;
Rect2i dest_rect ;
_get_clipped_src_and_dest_rects ( p_src , p_src_rect , p_dest , src_rect , dest_rect ) ;
2022-08-15 04:51:45 +02:00
if ( ! src_rect . has_area ( ) | | ! dest_rect . has_area ( ) ) {
2017-06-18 18:38:43 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2017-06-18 18:38:43 +02:00
Ref < Image > img = p_src ;
for ( int i = 0 ; i < dest_rect . size . y ; i + + ) {
for ( int j = 0 ; j < dest_rect . size . x ; j + + ) {
2021-07-05 10:48:25 +02:00
int src_x = src_rect . position . x + j ;
int src_y = src_rect . position . y + i ;
2017-06-18 18:38:43 +02:00
int dst_x = dest_rect . position . x + j ;
int dst_y = dest_rect . position . y + i ;
Color sc = img - > get_pixel ( src_x , src_y ) ;
2020-04-01 19:05:33 +02:00
if ( sc . a ! = 0 ) {
Color dc = get_pixel ( dst_x , dst_y ) ;
dc = dc . blend ( sc ) ;
set_pixel ( dst_x , dst_y , dc ) ;
}
2017-06-18 18:38:43 +02:00
}
}
}
2022-07-09 22:43:34 +02:00
void Image : : blend_rect_mask ( const Ref < Image > & p_src , const Ref < Image > & p_mask , const Rect2i & p_src_rect , const Point2i & p_dest ) {
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_MSG ( p_src . is_null ( ) , " It's not a reference to a valid Image object. " ) ;
ERR_FAIL_COND_MSG ( p_mask . is_null ( ) , " It's not a reference to a valid Image object. " ) ;
2017-06-18 18:38:43 +02:00
int dsize = data . size ( ) ;
int srcdsize = p_src - > data . size ( ) ;
int maskdsize = p_mask - > data . size ( ) ;
ERR_FAIL_COND ( dsize = = 0 ) ;
ERR_FAIL_COND ( srcdsize = = 0 ) ;
ERR_FAIL_COND ( maskdsize = = 0 ) ;
2019-09-25 10:28:50 +02:00
ERR_FAIL_COND_MSG ( p_src - > width ! = p_mask - > width , " Source image width is different from mask width. " ) ;
ERR_FAIL_COND_MSG ( p_src - > height ! = p_mask - > height , " Source image height is different from mask height. " ) ;
2017-06-18 18:38:43 +02:00
ERR_FAIL_COND ( format ! = p_src - > format ) ;
2021-07-05 10:48:25 +02:00
Rect2i src_rect ;
Rect2i dest_rect ;
_get_clipped_src_and_dest_rects ( p_src , p_src_rect , p_dest , src_rect , dest_rect ) ;
2022-08-15 04:51:45 +02:00
if ( ! src_rect . has_area ( ) | | ! dest_rect . has_area ( ) ) {
2017-06-18 18:38:43 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2017-06-18 18:38:43 +02:00
Ref < Image > img = p_src ;
Ref < Image > msk = p_mask ;
for ( int i = 0 ; i < dest_rect . size . y ; i + + ) {
for ( int j = 0 ; j < dest_rect . size . x ; j + + ) {
2021-07-05 10:48:25 +02:00
int src_x = src_rect . position . x + j ;
int src_y = src_rect . position . y + i ;
2017-06-18 18:38:43 +02:00
// If the mask's pixel is transparent then we skip it
//Color c = msk->get_pixel(src_x, src_y);
//if (c.a == 0) continue;
if ( msk - > get_pixel ( src_x , src_y ) . a ! = 0 ) {
int dst_x = dest_rect . position . x + j ;
int dst_y = dest_rect . position . y + i ;
Color sc = img - > get_pixel ( src_x , src_y ) ;
2020-04-01 19:05:33 +02:00
if ( sc . a ! = 0 ) {
Color dc = get_pixel ( dst_x , dst_y ) ;
dc = dc . blend ( sc ) ;
set_pixel ( dst_x , dst_y , dc ) ;
}
2017-06-18 18:38:43 +02:00
}
}
}
}
2021-09-08 10:16:11 +02:00
// Repeats `p_pixel` `p_count` times in consecutive memory.
// Results in the original pixel and `p_count - 1` subsequent copies of it.
void Image : : _repeat_pixel_over_subsequent_memory ( uint8_t * p_pixel , int p_pixel_size , int p_count ) {
int offset = 1 ;
for ( int stride = 1 ; offset + stride < = p_count ; stride * = 2 ) {
memcpy ( p_pixel + offset * p_pixel_size , p_pixel , stride * p_pixel_size ) ;
offset + = stride ;
}
if ( offset < p_count ) {
memcpy ( p_pixel + offset * p_pixel_size , p_pixel , ( p_count - offset ) * p_pixel_size ) ;
}
}
2021-09-07 10:27:32 +02:00
void Image : : fill ( const Color & p_color ) {
2022-10-24 10:28:10 +02:00
if ( data . size ( ) = = 0 ) {
return ;
}
2020-01-28 05:35:31 +01:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot fill in compressed or custom image formats. " ) ;
2017-06-18 18:38:43 +02:00
2021-09-07 10:27:32 +02:00
uint8_t * dst_data_ptr = data . ptrw ( ) ;
2017-06-18 18:38:43 +02:00
int pixel_size = get_format_pixel_size ( format ) ;
2021-09-07 10:27:32 +02:00
// Put first pixel with the format-aware API.
_set_color_at_ofs ( dst_data_ptr , 0 , p_color ) ;
2017-06-18 18:38:43 +02:00
2021-09-08 10:16:11 +02:00
_repeat_pixel_over_subsequent_memory ( dst_data_ptr , pixel_size , width * height ) ;
2017-06-18 18:38:43 +02:00
}
2022-07-09 22:43:34 +02:00
void Image : : fill_rect ( const Rect2i & p_rect , const Color & p_color ) {
2022-10-24 10:28:10 +02:00
if ( data . size ( ) = = 0 ) {
return ;
}
2021-09-07 10:27:32 +02:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot fill rect in compressed or custom image formats. " ) ;
Rect2i r = Rect2i ( 0 , 0 , width , height ) . intersection ( p_rect . abs ( ) ) ;
2022-08-15 04:51:45 +02:00
if ( ! r . has_area ( ) ) {
2021-09-07 10:27:32 +02:00
return ;
}
uint8_t * dst_data_ptr = data . ptrw ( ) ;
int pixel_size = get_format_pixel_size ( format ) ;
// Put first pixel with the format-aware API.
uint8_t * rect_first_pixel_ptr = & dst_data_ptr [ ( r . position . y * width + r . position . x ) * pixel_size ] ;
_set_color_at_ofs ( rect_first_pixel_ptr , 0 , p_color ) ;
2021-09-08 10:16:11 +02:00
if ( r . size . x = = width ) {
// No need to fill rows separately.
_repeat_pixel_over_subsequent_memory ( rect_first_pixel_ptr , pixel_size , width * r . size . y ) ;
} else {
_repeat_pixel_over_subsequent_memory ( rect_first_pixel_ptr , pixel_size , r . size . x ) ;
for ( int y = 1 ; y < r . size . y ; y + + ) {
memcpy ( rect_first_pixel_ptr + y * width * pixel_size , rect_first_pixel_ptr , r . size . x * pixel_size ) ;
2021-09-07 10:27:32 +02:00
}
}
}
2020-04-02 01:20:12 +02:00
ImageMemLoadFunc Image : : _png_mem_loader_func = nullptr ;
2023-09-06 01:44:18 +02:00
ImageMemLoadFunc Image : : _png_mem_unpacker_func = nullptr ;
2020-04-02 01:20:12 +02:00
ImageMemLoadFunc Image : : _jpg_mem_loader_func = nullptr ;
ImageMemLoadFunc Image : : _webp_mem_loader_func = nullptr ;
2020-05-21 13:46:44 +02:00
ImageMemLoadFunc Image : : _tga_mem_loader_func = nullptr ;
2020-10-20 23:25:58 +02:00
ImageMemLoadFunc Image : : _bmp_mem_loader_func = nullptr ;
2023-06-15 01:27:56 +02:00
ScalableImageMemLoadFunc Image : : _svg_scalable_mem_loader_func = nullptr ;
2023-04-28 18:26:01 +02:00
ImageMemLoadFunc Image : : _ktx_mem_loader_func = nullptr ;
2020-04-02 01:20:12 +02:00
2023-01-25 12:17:11 +01:00
void ( * Image : : _image_compress_bc_func ) ( Image * , Image : : UsedChannels ) = nullptr ;
void ( * Image : : _image_compress_bptc_func ) ( Image * , Image : : UsedChannels ) = nullptr ;
void ( * Image : : _image_compress_etc1_func ) ( Image * ) = nullptr ;
void ( * Image : : _image_compress_etc2_func ) ( Image * , Image : : UsedChannels ) = nullptr ;
void ( * Image : : _image_compress_astc_func ) ( Image * , Image : : ASTCFormat ) = nullptr ;
2020-04-02 01:20:12 +02:00
void ( * Image : : _image_decompress_bc ) ( Image * ) = nullptr ;
void ( * Image : : _image_decompress_bptc ) ( Image * ) = nullptr ;
void ( * Image : : _image_decompress_etc1 ) ( Image * ) = nullptr ;
void ( * Image : : _image_decompress_etc2 ) ( Image * ) = nullptr ;
2022-09-05 21:11:34 +02:00
void ( * Image : : _image_decompress_astc ) ( Image * ) = nullptr ;
2020-04-02 01:20:12 +02:00
2021-04-12 18:56:50 +02:00
Vector < uint8_t > ( * Image : : webp_lossy_packer ) ( const Ref < Image > & , float ) = nullptr ;
Vector < uint8_t > ( * Image : : webp_lossless_packer ) ( const Ref < Image > & ) = nullptr ;
Ref < Image > ( * Image : : webp_unpacker ) ( const Vector < uint8_t > & ) = nullptr ;
Vector < uint8_t > ( * Image : : png_packer ) ( const Ref < Image > & ) = nullptr ;
Ref < Image > ( * Image : : png_unpacker ) ( const Vector < uint8_t > & ) = nullptr ;
2020-04-02 01:20:12 +02:00
Vector < uint8_t > ( * Image : : basis_universal_packer ) ( const Ref < Image > & , Image : : UsedChannels ) = nullptr ;
Ref < Image > ( * Image : : basis_universal_unpacker ) ( const Vector < uint8_t > & ) = nullptr ;
2022-03-24 18:18:55 +01:00
Ref < Image > ( * Image : : basis_universal_unpacker_ptr ) ( const uint8_t * , int ) = nullptr ;
2017-05-17 12:36:47 +02:00
void Image : : _set_data ( const Dictionary & p_data ) {
ERR_FAIL_COND ( ! p_data . has ( " width " ) ) ;
ERR_FAIL_COND ( ! p_data . has ( " height " ) ) ;
ERR_FAIL_COND ( ! p_data . has ( " format " ) ) ;
ERR_FAIL_COND ( ! p_data . has ( " mipmaps " ) ) ;
ERR_FAIL_COND ( ! p_data . has ( " data " ) ) ;
int dwidth = p_data [ " width " ] ;
int dheight = p_data [ " height " ] ;
String dformat = p_data [ " format " ] ;
bool dmipmaps = p_data [ " mipmaps " ] ;
2020-02-17 22:06:54 +01:00
Vector < uint8_t > ddata = p_data [ " data " ] ;
2017-05-17 12:36:47 +02:00
Format ddformat = FORMAT_MAX ;
for ( int i = 0 ; i < FORMAT_MAX ; i + + ) {
if ( dformat = = get_format_name ( Format ( i ) ) ) {
ddformat = Format ( i ) ;
break ;
}
}
ERR_FAIL_COND ( ddformat = = FORMAT_MAX ) ;
2022-07-22 20:06:19 +02:00
initialize_data ( dwidth , dheight , dmipmaps , ddformat , ddata ) ;
2017-05-17 12:36:47 +02:00
}
Dictionary Image : : _get_data ( ) const {
Dictionary d ;
d [ " width " ] = width ;
d [ " height " ] = height ;
d [ " format " ] = get_format_name ( format ) ;
d [ " mipmaps " ] = mipmaps ;
d [ " data " ] = data ;
return d ;
}
2020-11-21 08:42:29 +01:00
Color Image : : get_pixelv ( const Point2i & p_point ) const {
return get_pixel ( p_point . x , p_point . y ) ;
2018-04-29 15:17:06 +02:00
}
2020-02-17 22:06:54 +01:00
Color Image : : _get_color_at_ofs ( const uint8_t * ptr , uint32_t ofs ) const {
2017-05-27 02:49:49 +02:00
switch ( format ) {
case FORMAT_L8 : {
float l = ptr [ ofs ] / 255.0 ;
return Color ( l , l , l , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_LA8 : {
float l = ptr [ ofs * 2 + 0 ] / 255.0 ;
float a = ptr [ ofs * 2 + 1 ] / 255.0 ;
return Color ( l , l , l , a ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_R8 : {
float r = ptr [ ofs ] / 255.0 ;
return Color ( r , 0 , 0 , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RG8 : {
float r = ptr [ ofs * 2 + 0 ] / 255.0 ;
float g = ptr [ ofs * 2 + 1 ] / 255.0 ;
return Color ( r , g , 0 , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGB8 : {
float r = ptr [ ofs * 3 + 0 ] / 255.0 ;
float g = ptr [ ofs * 3 + 1 ] / 255.0 ;
float b = ptr [ ofs * 3 + 2 ] / 255.0 ;
return Color ( r , g , b , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGBA8 : {
float r = ptr [ ofs * 4 + 0 ] / 255.0 ;
float g = ptr [ ofs * 4 + 1 ] / 255.0 ;
float b = ptr [ ofs * 4 + 2 ] / 255.0 ;
float a = ptr [ ofs * 4 + 3 ] / 255.0 ;
return Color ( r , g , b , a ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGBA4444 : {
uint16_t u = ( ( uint16_t * ) ptr ) [ ofs ] ;
2020-01-03 06:37:48 +01:00
float r = ( ( u > > 12 ) & 0xF ) / 15.0 ;
float g = ( ( u > > 8 ) & 0xF ) / 15.0 ;
float b = ( ( u > > 4 ) & 0xF ) / 15.0 ;
float a = ( u & 0xF ) / 15.0 ;
2017-05-27 02:49:49 +02:00
return Color ( r , g , b , a ) ;
2019-07-20 08:09:57 +02:00
}
2019-09-27 04:16:44 +02:00
case FORMAT_RGB565 : {
2017-05-27 02:49:49 +02:00
uint16_t u = ( ( uint16_t * ) ptr ) [ ofs ] ;
2019-09-27 04:16:44 +02:00
float r = ( u & 0x1F ) / 31.0 ;
float g = ( ( u > > 5 ) & 0x3F ) / 63.0 ;
float b = ( ( u > > 11 ) & 0x1F ) / 31.0 ;
return Color ( r , g , b , 1.0 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RF : {
float r = ( ( float * ) ptr ) [ ofs ] ;
return Color ( r , 0 , 0 , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGF : {
float r = ( ( float * ) ptr ) [ ofs * 2 + 0 ] ;
float g = ( ( float * ) ptr ) [ ofs * 2 + 1 ] ;
return Color ( r , g , 0 , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGBF : {
float r = ( ( float * ) ptr ) [ ofs * 3 + 0 ] ;
float g = ( ( float * ) ptr ) [ ofs * 3 + 1 ] ;
float b = ( ( float * ) ptr ) [ ofs * 3 + 2 ] ;
return Color ( r , g , b , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGBAF : {
float r = ( ( float * ) ptr ) [ ofs * 4 + 0 ] ;
float g = ( ( float * ) ptr ) [ ofs * 4 + 1 ] ;
float b = ( ( float * ) ptr ) [ ofs * 4 + 2 ] ;
float a = ( ( float * ) ptr ) [ ofs * 4 + 3 ] ;
return Color ( r , g , b , a ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RH : {
uint16_t r = ( ( uint16_t * ) ptr ) [ ofs ] ;
return Color ( Math : : half_to_float ( r ) , 0 , 0 , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGH : {
uint16_t r = ( ( uint16_t * ) ptr ) [ ofs * 2 + 0 ] ;
uint16_t g = ( ( uint16_t * ) ptr ) [ ofs * 2 + 1 ] ;
return Color ( Math : : half_to_float ( r ) , Math : : half_to_float ( g ) , 0 , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGBH : {
uint16_t r = ( ( uint16_t * ) ptr ) [ ofs * 3 + 0 ] ;
uint16_t g = ( ( uint16_t * ) ptr ) [ ofs * 3 + 1 ] ;
uint16_t b = ( ( uint16_t * ) ptr ) [ ofs * 3 + 2 ] ;
return Color ( Math : : half_to_float ( r ) , Math : : half_to_float ( g ) , Math : : half_to_float ( b ) , 1 ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGBAH : {
uint16_t r = ( ( uint16_t * ) ptr ) [ ofs * 4 + 0 ] ;
uint16_t g = ( ( uint16_t * ) ptr ) [ ofs * 4 + 1 ] ;
uint16_t b = ( ( uint16_t * ) ptr ) [ ofs * 4 + 2 ] ;
uint16_t a = ( ( uint16_t * ) ptr ) [ ofs * 4 + 3 ] ;
return Color ( Math : : half_to_float ( r ) , Math : : half_to_float ( g ) , Math : : half_to_float ( b ) , Math : : half_to_float ( a ) ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
case FORMAT_RGBE9995 : {
2018-08-22 04:56:04 +02:00
return Color : : from_rgbe9995 ( ( ( uint32_t * ) ptr ) [ ofs ] ) ;
2019-07-20 08:09:57 +02:00
}
2017-05-27 02:49:49 +02:00
default : {
2019-08-15 04:57:49 +02:00
ERR_FAIL_V_MSG ( Color ( ) , " Can't get_pixel() on compressed image, sorry. " ) ;
2017-05-27 02:49:49 +02:00
}
}
}
2020-01-27 00:09:40 +01:00
void Image : : _set_color_at_ofs ( uint8_t * ptr , uint32_t ofs , const Color & p_color ) {
2017-05-27 02:49:49 +02:00
switch ( format ) {
case FORMAT_L8 : {
2018-08-21 17:47:31 +02:00
ptr [ ofs ] = uint8_t ( CLAMP ( p_color . get_v ( ) * 255.0 , 0 , 255 ) ) ;
2017-05-27 02:49:49 +02:00
} break ;
case FORMAT_LA8 : {
2018-08-21 17:47:31 +02:00
ptr [ ofs * 2 + 0 ] = uint8_t ( CLAMP ( p_color . get_v ( ) * 255.0 , 0 , 255 ) ) ;
2017-05-27 02:49:49 +02:00
ptr [ ofs * 2 + 1 ] = uint8_t ( CLAMP ( p_color . a * 255.0 , 0 , 255 ) ) ;
} break ;
case FORMAT_R8 : {
ptr [ ofs ] = uint8_t ( CLAMP ( p_color . r * 255.0 , 0 , 255 ) ) ;
} break ;
case FORMAT_RG8 : {
ptr [ ofs * 2 + 0 ] = uint8_t ( CLAMP ( p_color . r * 255.0 , 0 , 255 ) ) ;
ptr [ ofs * 2 + 1 ] = uint8_t ( CLAMP ( p_color . g * 255.0 , 0 , 255 ) ) ;
} break ;
case FORMAT_RGB8 : {
ptr [ ofs * 3 + 0 ] = uint8_t ( CLAMP ( p_color . r * 255.0 , 0 , 255 ) ) ;
ptr [ ofs * 3 + 1 ] = uint8_t ( CLAMP ( p_color . g * 255.0 , 0 , 255 ) ) ;
ptr [ ofs * 3 + 2 ] = uint8_t ( CLAMP ( p_color . b * 255.0 , 0 , 255 ) ) ;
} break ;
case FORMAT_RGBA8 : {
ptr [ ofs * 4 + 0 ] = uint8_t ( CLAMP ( p_color . r * 255.0 , 0 , 255 ) ) ;
ptr [ ofs * 4 + 1 ] = uint8_t ( CLAMP ( p_color . g * 255.0 , 0 , 255 ) ) ;
ptr [ ofs * 4 + 2 ] = uint8_t ( CLAMP ( p_color . b * 255.0 , 0 , 255 ) ) ;
ptr [ ofs * 4 + 3 ] = uint8_t ( CLAMP ( p_color . a * 255.0 , 0 , 255 ) ) ;
} break ;
case FORMAT_RGBA4444 : {
uint16_t rgba = 0 ;
2020-01-03 06:37:48 +01:00
rgba = uint16_t ( CLAMP ( p_color . r * 15.0 , 0 , 15 ) ) < < 12 ;
rgba | = uint16_t ( CLAMP ( p_color . g * 15.0 , 0 , 15 ) ) < < 8 ;
rgba | = uint16_t ( CLAMP ( p_color . b * 15.0 , 0 , 15 ) ) < < 4 ;
rgba | = uint16_t ( CLAMP ( p_color . a * 15.0 , 0 , 15 ) ) ;
2017-05-27 02:49:49 +02:00
( ( uint16_t * ) ptr ) [ ofs ] = rgba ;
} break ;
2019-09-27 04:16:44 +02:00
case FORMAT_RGB565 : {
2017-05-27 02:49:49 +02:00
uint16_t rgba = 0 ;
2019-09-27 04:16:44 +02:00
rgba = uint16_t ( CLAMP ( p_color . r * 31.0 , 0 , 31 ) ) ;
rgba | = uint16_t ( CLAMP ( p_color . g * 63.0 , 0 , 33 ) ) < < 5 ;
rgba | = uint16_t ( CLAMP ( p_color . b * 31.0 , 0 , 31 ) ) < < 11 ;
2017-05-27 02:49:49 +02:00
( ( uint16_t * ) ptr ) [ ofs ] = rgba ;
} break ;
case FORMAT_RF : {
( ( float * ) ptr ) [ ofs ] = p_color . r ;
} break ;
case FORMAT_RGF : {
( ( float * ) ptr ) [ ofs * 2 + 0 ] = p_color . r ;
( ( float * ) ptr ) [ ofs * 2 + 1 ] = p_color . g ;
} break ;
case FORMAT_RGBF : {
( ( float * ) ptr ) [ ofs * 3 + 0 ] = p_color . r ;
( ( float * ) ptr ) [ ofs * 3 + 1 ] = p_color . g ;
( ( float * ) ptr ) [ ofs * 3 + 2 ] = p_color . b ;
} break ;
case FORMAT_RGBAF : {
( ( float * ) ptr ) [ ofs * 4 + 0 ] = p_color . r ;
( ( float * ) ptr ) [ ofs * 4 + 1 ] = p_color . g ;
( ( float * ) ptr ) [ ofs * 4 + 2 ] = p_color . b ;
( ( float * ) ptr ) [ ofs * 4 + 3 ] = p_color . a ;
} break ;
case FORMAT_RH : {
( ( uint16_t * ) ptr ) [ ofs ] = Math : : make_half_float ( p_color . r ) ;
} break ;
case FORMAT_RGH : {
( ( uint16_t * ) ptr ) [ ofs * 2 + 0 ] = Math : : make_half_float ( p_color . r ) ;
( ( uint16_t * ) ptr ) [ ofs * 2 + 1 ] = Math : : make_half_float ( p_color . g ) ;
} break ;
case FORMAT_RGBH : {
( ( uint16_t * ) ptr ) [ ofs * 3 + 0 ] = Math : : make_half_float ( p_color . r ) ;
( ( uint16_t * ) ptr ) [ ofs * 3 + 1 ] = Math : : make_half_float ( p_color . g ) ;
( ( uint16_t * ) ptr ) [ ofs * 3 + 2 ] = Math : : make_half_float ( p_color . b ) ;
} break ;
case FORMAT_RGBAH : {
( ( uint16_t * ) ptr ) [ ofs * 4 + 0 ] = Math : : make_half_float ( p_color . r ) ;
( ( uint16_t * ) ptr ) [ ofs * 4 + 1 ] = Math : : make_half_float ( p_color . g ) ;
( ( uint16_t * ) ptr ) [ ofs * 4 + 2 ] = Math : : make_half_float ( p_color . b ) ;
( ( uint16_t * ) ptr ) [ ofs * 4 + 3 ] = Math : : make_half_float ( p_color . a ) ;
} break ;
case FORMAT_RGBE9995 : {
2017-05-29 02:46:48 +02:00
( ( uint32_t * ) ptr ) [ ofs ] = p_color . to_rgbe9995 ( ) ;
2017-05-27 02:49:49 +02:00
} break ;
default : {
2019-08-15 04:57:49 +02:00
ERR_FAIL_MSG ( " Can't set_pixel() on compressed image, sorry. " ) ;
2017-05-27 02:49:49 +02:00
}
}
}
2020-01-27 00:09:40 +01:00
Color Image : : get_pixel ( int p_x , int p_y ) const {
# ifdef DEBUG_ENABLED
ERR_FAIL_INDEX_V ( p_x , width , Color ( ) ) ;
ERR_FAIL_INDEX_V ( p_y , height , Color ( ) ) ;
# endif
uint32_t ofs = p_y * width + p_x ;
2020-02-17 22:06:54 +01:00
return _get_color_at_ofs ( data . ptr ( ) , ofs ) ;
2020-01-27 00:09:40 +01:00
}
2020-11-21 08:42:29 +01:00
void Image : : set_pixelv ( const Point2i & p_point , const Color & p_color ) {
set_pixel ( p_point . x , p_point . y , p_color ) ;
2020-01-27 00:09:40 +01:00
}
void Image : : set_pixel ( int p_x , int p_y , const Color & p_color ) {
# ifdef DEBUG_ENABLED
ERR_FAIL_INDEX ( p_x , width ) ;
ERR_FAIL_INDEX ( p_y , height ) ;
# endif
uint32_t ofs = p_y * width + p_x ;
2020-02-17 22:06:54 +01:00
_set_color_at_ofs ( data . ptrw ( ) , ofs , p_color ) ;
2020-01-27 00:09:40 +01:00
}
2021-02-12 16:16:37 +01:00
void Image : : adjust_bcs ( float p_brightness , float p_contrast , float p_saturation ) {
2021-06-27 14:52:42 +02:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot adjust_bcs in compressed or custom image formats. " ) ;
2021-02-12 16:16:37 +01:00
uint8_t * w = data . ptrw ( ) ;
uint32_t pixel_size = get_format_pixel_size ( format ) ;
uint32_t pixel_count = data . size ( ) / pixel_size ;
for ( uint32_t i = 0 ; i < pixel_count ; i + + ) {
Color c = _get_color_at_ofs ( w , i ) ;
Vector3 rgb ( c . r , c . g , c . b ) ;
rgb * = p_brightness ;
rgb = Vector3 ( 0.5 , 0.5 , 0.5 ) . lerp ( rgb , p_contrast ) ;
float center = ( rgb . x + rgb . y + rgb . z ) / 3.0 ;
rgb = Vector3 ( center , center , center ) . lerp ( rgb , p_saturation ) ;
c . r = rgb . x ;
c . g = rgb . y ;
c . b = rgb . z ;
_set_color_at_ofs ( w , i , c ) ;
}
}
2022-03-24 18:18:55 +01:00
Image : : UsedChannels Image : : detect_used_channels ( CompressSource p_source ) const {
2019-09-27 04:16:44 +02:00
ERR_FAIL_COND_V ( data . size ( ) = = 0 , USED_CHANNELS_RGBA ) ;
ERR_FAIL_COND_V ( is_compressed ( ) , USED_CHANNELS_RGBA ) ;
2017-05-27 02:49:49 +02:00
bool r = false , g = false , b = false , a = false , c = false ;
2020-02-17 22:06:54 +01:00
2021-03-26 19:14:12 +01:00
const uint8_t * data_ptr = data . ptr ( ) ;
2017-05-27 02:49:49 +02:00
2021-03-26 19:14:12 +01:00
uint32_t data_total = width * height ;
2017-05-27 02:49:49 +02:00
2021-03-26 19:14:12 +01:00
for ( uint32_t i = 0 ; i < data_total ; i + + ) {
Color col = _get_color_at_ofs ( data_ptr , i ) ;
if ( col . r > 0.001 ) {
r = true ;
}
if ( col . g > 0.001 ) {
g = true ;
}
if ( col . b > 0.001 ) {
b = true ;
}
if ( col . a < 0.999 ) {
a = true ;
}
if ( col . r ! = col . b | | col . r ! = col . g | | col . b ! = col . g ) {
c = true ;
2017-05-27 02:49:49 +02:00
}
}
2019-09-27 04:16:44 +02:00
UsedChannels used_channels ;
2017-05-27 02:49:49 +02:00
2020-05-14 16:41:43 +02:00
if ( ! c & & ! a ) {
2019-09-27 04:16:44 +02:00
used_channels = USED_CHANNELS_L ;
2020-05-14 16:41:43 +02:00
} else if ( ! c & & a ) {
2019-09-27 04:16:44 +02:00
used_channels = USED_CHANNELS_LA ;
2020-05-14 16:41:43 +02:00
} else if ( r & & ! g & & ! b & & ! a ) {
2019-09-27 04:16:44 +02:00
used_channels = USED_CHANNELS_R ;
2020-05-14 16:41:43 +02:00
} else if ( r & & g & & ! b & & ! a ) {
2019-09-27 04:16:44 +02:00
used_channels = USED_CHANNELS_RG ;
2020-05-14 16:41:43 +02:00
} else if ( r & & g & & b & & ! a ) {
2019-09-27 04:16:44 +02:00
used_channels = USED_CHANNELS_RGB ;
2020-05-14 16:41:43 +02:00
} else {
2019-09-27 04:16:44 +02:00
used_channels = USED_CHANNELS_RGBA ;
2020-05-14 16:41:43 +02:00
}
2017-05-27 02:49:49 +02:00
2019-09-27 04:16:44 +02:00
if ( p_source = = COMPRESS_SOURCE_SRGB & & ( used_channels = = USED_CHANNELS_R | | used_channels = = USED_CHANNELS_RG ) ) {
//R and RG do not support SRGB
used_channels = USED_CHANNELS_RGB ;
}
2017-05-27 02:49:49 +02:00
2019-09-27 04:16:44 +02:00
if ( p_source = = COMPRESS_SOURCE_NORMAL ) {
//use RG channels only for normal
used_channels = USED_CHANNELS_RG ;
}
2017-05-27 02:49:49 +02:00
2019-09-27 04:16:44 +02:00
return used_channels ;
2017-05-27 02:49:49 +02:00
}
2018-08-06 19:56:06 +02:00
void Image : : optimize_channels ( ) {
2019-09-27 04:16:44 +02:00
switch ( detect_used_channels ( ) ) {
2020-05-10 13:00:47 +02:00
case USED_CHANNELS_L :
convert ( FORMAT_L8 ) ;
break ;
case USED_CHANNELS_LA :
convert ( FORMAT_LA8 ) ;
break ;
case USED_CHANNELS_R :
convert ( FORMAT_R8 ) ;
break ;
case USED_CHANNELS_RG :
convert ( FORMAT_RG8 ) ;
break ;
case USED_CHANNELS_RGB :
convert ( FORMAT_RGB8 ) ;
break ;
case USED_CHANNELS_RGBA :
convert ( FORMAT_RGBA8 ) ;
break ;
2018-08-06 19:56:06 +02:00
}
}
2017-05-17 12:36:47 +02:00
void Image : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " get_width " ) , & Image : : get_width ) ;
ClassDB : : bind_method ( D_METHOD ( " get_height " ) , & Image : : get_height ) ;
2017-08-26 13:45:03 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_size " ) , & Image : : get_size ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " has_mipmaps " ) , & Image : : has_mipmaps ) ;
ClassDB : : bind_method ( D_METHOD ( " get_format " ) , & Image : : get_format ) ;
ClassDB : : bind_method ( D_METHOD ( " get_data " ) , & Image : : get_data ) ;
ClassDB : : bind_method ( D_METHOD ( " convert " , " format " ) , & Image : : convert ) ;
2023-09-19 12:53:09 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_mipmap_count " ) , & Image : : get_mipmap_count ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_mipmap_offset " , " mipmap " ) , & Image : : get_mipmap_offset ) ;
2020-12-18 15:14:55 +01:00
ClassDB : : bind_method ( D_METHOD ( " resize_to_po2 " , " square " , " interpolation " ) , & Image : : resize_to_po2 , DEFVAL ( false ) , DEFVAL ( INTERPOLATE_BILINEAR ) ) ;
2017-05-17 18:45:56 +02:00
ClassDB : : bind_method ( D_METHOD ( " resize " , " width " , " height " , " interpolation " ) , & Image : : resize , DEFVAL ( INTERPOLATE_BILINEAR ) ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " shrink_x2 " ) , & Image : : shrink_x2 ) ;
ClassDB : : bind_method ( D_METHOD ( " crop " , " width " , " height " ) , & Image : : crop ) ;
ClassDB : : bind_method ( D_METHOD ( " flip_x " ) , & Image : : flip_x ) ;
ClassDB : : bind_method ( D_METHOD ( " flip_y " ) , & Image : : flip_y ) ;
2018-04-30 02:51:37 +02:00
ClassDB : : bind_method ( D_METHOD ( " generate_mipmaps " , " renormalize " ) , & Image : : generate_mipmaps , DEFVAL ( false ) ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " clear_mipmaps " ) , & Image : : clear_mipmaps ) ;
2022-07-22 20:06:19 +02:00
ClassDB : : bind_static_method ( " Image " , D_METHOD ( " create " , " width " , " height " , " use_mipmaps " , " format " ) , & Image : : create_empty ) ;
ClassDB : : bind_static_method ( " Image " , D_METHOD ( " create_from_data " , " width " , " height " , " use_mipmaps " , " format " , " data " ) , & Image : : create_from_data ) ;
ClassDB : : bind_method ( D_METHOD ( " set_data " , " width " , " height " , " use_mipmaps " , " format " , " data " ) , & Image : : set_data ) ;
2017-05-17 12:36:47 +02:00
2020-12-15 13:04:21 +01:00
ClassDB : : bind_method ( D_METHOD ( " is_empty " ) , & Image : : is_empty ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " load " , " path " ) , & Image : : load ) ;
2022-05-04 01:49:20 +02:00
ClassDB : : bind_static_method ( " Image " , D_METHOD ( " load_from_file " , " path " ) , & Image : : load_from_file ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " save_png " , " path " ) , & Image : : save_png ) ;
2020-05-20 13:43:24 +02:00
ClassDB : : bind_method ( D_METHOD ( " save_png_to_buffer " ) , & Image : : save_png_to_buffer ) ;
Implement Running Godot as Movie Writer
* Allows running the game in "movie writer" mode.
* It ensures entirely stable framerate, so your run can be saved stable and with proper sound (which is impossible if your CPU/GPU can't sustain doing this in real-time).
* If disabling vsync, it can save movies faster than the game is run, but if you want to control the interaction it can get difficult.
* Implements a simple, default MJPEG writer.
This new features has two main use cases, which have high demand:
* Saving game videos in high quality and ensuring the frame rate is *completely* stable, always.
* Using Godot as a tool to make movies and animations (which is ideal if you want interaction, or creating them procedurally. No other software is as good for this).
**Note**: This feature **IS NOT** for capturing real-time footage. Use something like OBS, SimpleScreenRecorder or FRAPS to achieve that, as they do a much better job at intercepting the compositor than Godot can probably do using Vulkan or OpenGL natively. If your game runs near real-time when capturing, you can still use this feature but it will play no sound (sound will be saved directly).
Usage:
$ godot --write-movie movie.avi [scene_file.tscn]
Missing:
* Options for configuring video writing via GLOBAL_DEF
* UI Menu for launching with this mode from the editor.
* Add to list of command line options.
* Add a feature tag to override configurations when movie writing (fantastic for saving videos with highest quality settings).
2022-06-17 00:55:19 +02:00
ClassDB : : bind_method ( D_METHOD ( " save_jpg " , " path " , " quality " ) , & Image : : save_jpg , DEFVAL ( 0.75 ) ) ;
ClassDB : : bind_method ( D_METHOD ( " save_jpg_to_buffer " , " quality " ) , & Image : : save_jpg_to_buffer , DEFVAL ( 0.75 ) ) ;
2019-08-03 02:07:13 +02:00
ClassDB : : bind_method ( D_METHOD ( " save_exr " , " path " , " grayscale " ) , & Image : : save_exr , DEFVAL ( false ) ) ;
2022-06-20 03:55:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " save_exr_to_buffer " , " grayscale " ) , & Image : : save_exr_to_buffer , DEFVAL ( false ) ) ;
2022-06-04 23:05:55 +02:00
ClassDB : : bind_method ( D_METHOD ( " save_webp " , " path " , " lossy " , " quality " ) , & Image : : save_webp , DEFVAL ( false ) , DEFVAL ( 0.75f ) ) ;
ClassDB : : bind_method ( D_METHOD ( " save_webp_to_buffer " , " lossy " , " quality " ) , & Image : : save_webp_to_buffer , DEFVAL ( false ) , DEFVAL ( 0.75f ) ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " detect_alpha " ) , & Image : : detect_alpha ) ;
ClassDB : : bind_method ( D_METHOD ( " is_invisible " ) , & Image : : is_invisible ) ;
2019-09-27 04:16:44 +02:00
ClassDB : : bind_method ( D_METHOD ( " detect_used_channels " , " source " ) , & Image : : detect_used_channels , DEFVAL ( COMPRESS_SOURCE_GENERIC ) ) ;
2023-01-25 12:17:11 +01:00
ClassDB : : bind_method ( D_METHOD ( " compress " , " mode " , " source " , " astc_format " ) , & Image : : compress , DEFVAL ( COMPRESS_SOURCE_GENERIC ) , DEFVAL ( ASTC_FORMAT_4x4 ) ) ;
ClassDB : : bind_method ( D_METHOD ( " compress_from_channels " , " mode " , " channels " , " astc_format " ) , & Image : : compress_from_channels , DEFVAL ( ASTC_FORMAT_4x4 ) ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " decompress " ) , & Image : : decompress ) ;
ClassDB : : bind_method ( D_METHOD ( " is_compressed " ) , & Image : : is_compressed ) ;
2022-07-14 21:20:12 +02:00
ClassDB : : bind_method ( D_METHOD ( " rotate_90 " , " direction " ) , & Image : : rotate_90 ) ;
ClassDB : : bind_method ( D_METHOD ( " rotate_180 " ) , & Image : : rotate_180 ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " fix_alpha_edges " ) , & Image : : fix_alpha_edges ) ;
ClassDB : : bind_method ( D_METHOD ( " premultiply_alpha " ) , & Image : : premultiply_alpha ) ;
ClassDB : : bind_method ( D_METHOD ( " srgb_to_linear " ) , & Image : : srgb_to_linear ) ;
2020-12-23 10:34:26 +01:00
ClassDB : : bind_method ( D_METHOD ( " normal_map_to_xy " ) , & Image : : normal_map_to_xy ) ;
2018-05-30 21:14:07 +02:00
ClassDB : : bind_method ( D_METHOD ( " rgbe_to_srgb " ) , & Image : : rgbe_to_srgb ) ;
2020-12-23 10:34:26 +01:00
ClassDB : : bind_method ( D_METHOD ( " bump_map_to_normal_map " , " bump_scale " ) , & Image : : bump_map_to_normal_map , DEFVAL ( 1.0 ) ) ;
2017-05-17 12:36:47 +02:00
2022-01-18 13:39:55 +01:00
ClassDB : : bind_method ( D_METHOD ( " compute_image_metrics " , " compared_image " , " use_luma " ) , & Image : : compute_image_metrics ) ;
2017-08-09 13:19:41 +02:00
ClassDB : : bind_method ( D_METHOD ( " blit_rect " , " src " , " src_rect " , " dst " ) , & Image : : blit_rect ) ;
ClassDB : : bind_method ( D_METHOD ( " blit_rect_mask " , " src " , " mask " , " src_rect " , " dst " ) , & Image : : blit_rect_mask ) ;
ClassDB : : bind_method ( D_METHOD ( " blend_rect " , " src " , " src_rect " , " dst " ) , & Image : : blend_rect ) ;
ClassDB : : bind_method ( D_METHOD ( " blend_rect_mask " , " src " , " mask " , " src_rect " , " dst " ) , & Image : : blend_rect_mask ) ;
2017-06-18 18:38:43 +02:00
ClassDB : : bind_method ( D_METHOD ( " fill " , " color " ) , & Image : : fill ) ;
2021-09-07 10:27:32 +02:00
ClassDB : : bind_method ( D_METHOD ( " fill_rect " , " rect " , " color " ) , & Image : : fill_rect ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_used_rect " ) , & Image : : get_used_rect ) ;
2022-09-18 02:19:55 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_region " , " region " ) , & Image : : get_region ) ;
2017-05-17 12:36:47 +02:00
2017-08-09 13:19:41 +02:00
ClassDB : : bind_method ( D_METHOD ( " copy_from " , " src " ) , & Image : : copy_internals_from ) ;
2017-05-17 12:36:47 +02:00
ClassDB : : bind_method ( D_METHOD ( " _set_data " , " data " ) , & Image : : _set_data ) ;
ClassDB : : bind_method ( D_METHOD ( " _get_data " ) , & Image : : _get_data ) ;
2020-11-21 08:42:29 +01:00
ClassDB : : bind_method ( D_METHOD ( " get_pixelv " , " point " ) , & Image : : get_pixelv ) ;
2017-05-27 02:49:49 +02:00
ClassDB : : bind_method ( D_METHOD ( " get_pixel " , " x " , " y " ) , & Image : : get_pixel ) ;
2020-11-21 08:42:29 +01:00
ClassDB : : bind_method ( D_METHOD ( " set_pixelv " , " point " , " color " ) , & Image : : set_pixelv ) ;
2018-04-29 15:17:06 +02:00
ClassDB : : bind_method ( D_METHOD ( " set_pixel " , " x " , " y " , " color " ) , & Image : : set_pixel ) ;
2017-05-27 02:49:49 +02:00
2021-02-12 16:16:37 +01:00
ClassDB : : bind_method ( D_METHOD ( " adjust_bcs " , " brightness " , " contrast " , " saturation " ) , & Image : : adjust_bcs ) ;
2017-12-20 21:16:21 +01:00
ClassDB : : bind_method ( D_METHOD ( " load_png_from_buffer " , " buffer " ) , & Image : : load_png_from_buffer ) ;
ClassDB : : bind_method ( D_METHOD ( " load_jpg_from_buffer " , " buffer " ) , & Image : : load_jpg_from_buffer ) ;
2018-07-14 19:42:37 +02:00
ClassDB : : bind_method ( D_METHOD ( " load_webp_from_buffer " , " buffer " ) , & Image : : load_webp_from_buffer ) ;
2020-05-21 13:46:44 +02:00
ClassDB : : bind_method ( D_METHOD ( " load_tga_from_buffer " , " buffer " ) , & Image : : load_tga_from_buffer ) ;
2020-10-20 23:25:58 +02:00
ClassDB : : bind_method ( D_METHOD ( " load_bmp_from_buffer " , " buffer " ) , & Image : : load_bmp_from_buffer ) ;
2023-04-28 18:26:01 +02:00
ClassDB : : bind_method ( D_METHOD ( " load_ktx_from_buffer " , " buffer " ) , & Image : : load_ktx_from_buffer ) ;
2017-12-20 21:16:21 +01:00
2023-06-15 01:27:56 +02:00
ClassDB : : bind_method ( D_METHOD ( " load_svg_from_buffer " , " buffer " , " scale " ) , & Image : : load_svg_from_buffer , DEFVAL ( 1.0 ) ) ;
ClassDB : : bind_method ( D_METHOD ( " load_svg_from_string " , " svg_str " , " scale " ) , & Image : : load_svg_from_string , DEFVAL ( 1.0 ) ) ;
2017-05-17 12:36:47 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : DICTIONARY , " data " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_STORAGE ) , " _set_data " , " _get_data " ) ;
2019-01-28 15:33:56 +01:00
BIND_CONSTANT ( MAX_WIDTH ) ;
BIND_CONSTANT ( MAX_HEIGHT ) ;
2017-08-20 17:45:01 +02:00
BIND_ENUM_CONSTANT ( FORMAT_L8 ) ; //luminance
BIND_ENUM_CONSTANT ( FORMAT_LA8 ) ; //luminance-alpha
BIND_ENUM_CONSTANT ( FORMAT_R8 ) ;
BIND_ENUM_CONSTANT ( FORMAT_RG8 ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGB8 ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGBA8 ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGBA4444 ) ;
2019-09-27 04:16:44 +02:00
BIND_ENUM_CONSTANT ( FORMAT_RGB565 ) ;
2017-08-20 17:45:01 +02:00
BIND_ENUM_CONSTANT ( FORMAT_RF ) ; //float
BIND_ENUM_CONSTANT ( FORMAT_RGF ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGBF ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGBAF ) ;
BIND_ENUM_CONSTANT ( FORMAT_RH ) ; //half float
BIND_ENUM_CONSTANT ( FORMAT_RGH ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGBH ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGBAH ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGBE9995 ) ;
BIND_ENUM_CONSTANT ( FORMAT_DXT1 ) ; //s3tc bc1
BIND_ENUM_CONSTANT ( FORMAT_DXT3 ) ; //bc2
BIND_ENUM_CONSTANT ( FORMAT_DXT5 ) ; //bc3
BIND_ENUM_CONSTANT ( FORMAT_RGTC_R ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGTC_RG ) ;
BIND_ENUM_CONSTANT ( FORMAT_BPTC_RGBA ) ; //btpc bc6h
BIND_ENUM_CONSTANT ( FORMAT_BPTC_RGBF ) ; //float /
BIND_ENUM_CONSTANT ( FORMAT_BPTC_RGBFU ) ; //unsigned float
BIND_ENUM_CONSTANT ( FORMAT_ETC ) ; //etc1
BIND_ENUM_CONSTANT ( FORMAT_ETC2_R11 ) ; //etc2
BIND_ENUM_CONSTANT ( FORMAT_ETC2_R11S ) ; //signed ); NOT srgb.
BIND_ENUM_CONSTANT ( FORMAT_ETC2_RG11 ) ;
BIND_ENUM_CONSTANT ( FORMAT_ETC2_RG11S ) ;
BIND_ENUM_CONSTANT ( FORMAT_ETC2_RGB8 ) ;
BIND_ENUM_CONSTANT ( FORMAT_ETC2_RGBA8 ) ;
BIND_ENUM_CONSTANT ( FORMAT_ETC2_RGB8A1 ) ;
2019-09-27 04:16:44 +02:00
BIND_ENUM_CONSTANT ( FORMAT_ETC2_RA_AS_RG ) ;
BIND_ENUM_CONSTANT ( FORMAT_DXT5_RA_AS_RG ) ;
2022-09-05 21:11:34 +02:00
BIND_ENUM_CONSTANT ( FORMAT_ASTC_4x4 ) ;
BIND_ENUM_CONSTANT ( FORMAT_ASTC_4x4_HDR ) ;
BIND_ENUM_CONSTANT ( FORMAT_ASTC_8x8 ) ;
BIND_ENUM_CONSTANT ( FORMAT_ASTC_8x8_HDR ) ;
2017-08-20 17:45:01 +02:00
BIND_ENUM_CONSTANT ( FORMAT_MAX ) ;
BIND_ENUM_CONSTANT ( INTERPOLATE_NEAREST ) ;
BIND_ENUM_CONSTANT ( INTERPOLATE_BILINEAR ) ;
BIND_ENUM_CONSTANT ( INTERPOLATE_CUBIC ) ;
2018-06-01 21:53:35 +02:00
BIND_ENUM_CONSTANT ( INTERPOLATE_TRILINEAR ) ;
2019-05-05 14:03:52 +02:00
BIND_ENUM_CONSTANT ( INTERPOLATE_LANCZOS ) ;
2017-08-20 17:45:01 +02:00
BIND_ENUM_CONSTANT ( ALPHA_NONE ) ;
BIND_ENUM_CONSTANT ( ALPHA_BIT ) ;
BIND_ENUM_CONSTANT ( ALPHA_BLEND ) ;
BIND_ENUM_CONSTANT ( COMPRESS_S3TC ) ;
BIND_ENUM_CONSTANT ( COMPRESS_ETC ) ;
BIND_ENUM_CONSTANT ( COMPRESS_ETC2 ) ;
2020-12-10 23:01:00 +01:00
BIND_ENUM_CONSTANT ( COMPRESS_BPTC ) ;
2023-04-13 09:39:03 +02:00
BIND_ENUM_CONSTANT ( COMPRESS_ASTC ) ;
BIND_ENUM_CONSTANT ( COMPRESS_MAX ) ;
2017-08-20 17:45:01 +02:00
2019-09-27 04:16:44 +02:00
BIND_ENUM_CONSTANT ( USED_CHANNELS_L ) ;
BIND_ENUM_CONSTANT ( USED_CHANNELS_LA ) ;
BIND_ENUM_CONSTANT ( USED_CHANNELS_R ) ;
BIND_ENUM_CONSTANT ( USED_CHANNELS_RG ) ;
BIND_ENUM_CONSTANT ( USED_CHANNELS_RGB ) ;
BIND_ENUM_CONSTANT ( USED_CHANNELS_RGBA ) ;
2017-08-20 17:45:01 +02:00
BIND_ENUM_CONSTANT ( COMPRESS_SOURCE_GENERIC ) ;
BIND_ENUM_CONSTANT ( COMPRESS_SOURCE_SRGB ) ;
BIND_ENUM_CONSTANT ( COMPRESS_SOURCE_NORMAL ) ;
2022-09-05 21:11:34 +02:00
BIND_ENUM_CONSTANT ( ASTC_FORMAT_4x4 ) ;
BIND_ENUM_CONSTANT ( ASTC_FORMAT_8x8 ) ;
2017-05-17 12:36:47 +02:00
}
2014-02-10 02:10:30 +01:00
2023-01-25 12:17:11 +01:00
void Image : : set_compress_bc_func ( void ( * p_compress_func ) ( Image * , UsedChannels ) ) {
2014-02-10 02:10:30 +01:00
_image_compress_bc_func = p_compress_func ;
}
2023-01-25 12:17:11 +01:00
void Image : : set_compress_bptc_func ( void ( * p_compress_func ) ( Image * , UsedChannels ) ) {
2018-08-22 04:56:04 +02:00
_image_compress_bptc_func = p_compress_func ;
}
2020-12-23 10:34:26 +01:00
void Image : : normal_map_to_xy ( ) {
2016-10-03 21:33:42 +02:00
convert ( Image : : FORMAT_RGBA8 ) ;
2014-10-03 13:58:41 +02:00
{
int len = data . size ( ) / 4 ;
2020-02-17 22:06:54 +01:00
uint8_t * data_ptr = data . ptrw ( ) ;
2014-10-03 13:58:41 +02:00
for ( int i = 0 ; i < len ; i + + ) {
data_ptr [ ( i < < 2 ) + 3 ] = data_ptr [ ( i < < 2 ) + 0 ] ; //x to w
data_ptr [ ( i < < 2 ) + 0 ] = data_ptr [ ( i < < 2 ) + 1 ] ; //y to xz
data_ptr [ ( i < < 2 ) + 2 ] = data_ptr [ ( i < < 2 ) + 1 ] ;
}
}
2016-10-03 21:33:42 +02:00
convert ( Image : : FORMAT_LA8 ) ;
2014-10-03 13:58:41 +02:00
}
2018-05-30 21:14:07 +02:00
Ref < Image > Image : : rgbe_to_srgb ( ) {
2020-05-14 16:41:43 +02:00
if ( data . size ( ) = = 0 ) {
2018-05-30 21:14:07 +02:00
return Ref < Image > ( ) ;
2020-05-14 16:41:43 +02:00
}
2018-05-30 21:14:07 +02:00
ERR_FAIL_COND_V ( format ! = FORMAT_RGBE9995 , Ref < Image > ( ) ) ;
2022-07-22 20:06:19 +02:00
Ref < Image > new_image = create_empty ( width , height , false , Image : : FORMAT_RGB8 ) ;
2018-05-30 21:14:07 +02:00
for ( int row = 0 ; row < height ; row + + ) {
for ( int col = 0 ; col < width ; col + + ) {
2022-04-13 10:37:22 +02:00
new_image - > set_pixel ( col , row , get_pixel ( col , row ) . linear_to_srgb ( ) ) ;
2018-05-30 21:14:07 +02:00
}
}
if ( has_mipmaps ( ) ) {
new_image - > generate_mipmaps ( ) ;
}
return new_image ;
}
2019-09-27 04:16:44 +02:00
Ref < Image > Image : : get_image_from_mipmap ( int p_mipamp ) const {
int ofs , size , w , h ;
get_mipmap_offset_size_and_dimensions ( p_mipamp , ofs , size , w , h ) ;
2020-02-17 22:06:54 +01:00
Vector < uint8_t > new_data ;
2019-09-27 04:16:44 +02:00
new_data . resize ( size ) ;
2020-02-17 22:06:54 +01:00
2019-09-27 04:16:44 +02:00
{
2020-02-17 22:06:54 +01:00
uint8_t * wr = new_data . ptrw ( ) ;
const uint8_t * rd = data . ptr ( ) ;
2021-04-27 16:19:21 +02:00
memcpy ( wr , rd + ofs , size ) ;
2019-09-27 04:16:44 +02:00
}
Ref < Image > image ;
2021-06-18 00:03:09 +02:00
image . instantiate ( ) ;
2019-09-27 04:16:44 +02:00
image - > width = w ;
image - > height = h ;
image - > format = format ;
image - > data = new_data ;
2020-02-17 22:06:54 +01:00
2019-09-27 04:16:44 +02:00
image - > mipmaps = false ;
return image ;
}
2020-12-23 10:34:26 +01:00
void Image : : bump_map_to_normal_map ( float bump_scale ) {
2018-02-20 23:43:58 +01:00
ERR_FAIL_COND ( ! _can_modify ( format ) ) ;
2022-11-01 11:54:04 +01:00
clear_mipmaps ( ) ;
2018-02-20 23:43:58 +01:00
convert ( Image : : FORMAT_RF ) ;
2020-02-17 22:06:54 +01:00
Vector < uint8_t > result_image ; //rgba output
2018-02-24 01:59:02 +01:00
result_image . resize ( width * height * 4 ) ;
2018-02-20 23:43:58 +01:00
{
2020-02-17 22:06:54 +01:00
const uint8_t * rp = data . ptr ( ) ;
uint8_t * wp = result_image . ptrw ( ) ;
2018-02-20 23:43:58 +01:00
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL ( rp ) ;
2019-10-28 08:07:29 +01:00
2020-02-17 22:06:54 +01:00
unsigned char * write_ptr = wp ;
float * read_ptr = ( float * ) rp ;
2018-02-20 23:43:58 +01:00
for ( int ty = 0 ; ty < height ; ty + + ) {
2018-02-24 01:59:02 +01:00
int py = ty + 1 ;
2020-05-14 16:41:43 +02:00
if ( py > = height ) {
2018-02-24 01:59:02 +01:00
py - = height ;
2020-05-14 16:41:43 +02:00
}
2018-02-20 23:43:58 +01:00
for ( int tx = 0 ; tx < width ; tx + + ) {
2018-02-24 01:59:02 +01:00
int px = tx + 1 ;
2020-05-14 16:41:43 +02:00
if ( px > = width ) {
2018-02-24 01:59:02 +01:00
px - = width ;
2020-05-14 16:41:43 +02:00
}
2018-02-24 01:59:02 +01:00
float here = read_ptr [ ty * width + tx ] ;
float to_right = read_ptr [ ty * width + px ] ;
float above = read_ptr [ py * width + tx ] ;
Vector3 up = Vector3 ( 0 , 1 , ( here - above ) * bump_scale ) ;
Vector3 across = Vector3 ( 1 , 0 , ( to_right - here ) * bump_scale ) ;
2018-02-20 23:43:58 +01:00
Vector3 normal = across . cross ( up ) ;
normal . normalize ( ) ;
2018-03-15 22:22:48 +01:00
write_ptr [ ( ( ty * width + tx ) < < 2 ) + 0 ] = ( 127.5 + normal . x * 127.5 ) ;
write_ptr [ ( ( ty * width + tx ) < < 2 ) + 1 ] = ( 127.5 + normal . y * 127.5 ) ;
write_ptr [ ( ( ty * width + tx ) < < 2 ) + 2 ] = ( 127.5 + normal . z * 127.5 ) ;
2018-02-24 01:59:02 +01:00
write_ptr [ ( ( ty * width + tx ) < < 2 ) + 3 ] = 255 ;
2018-02-20 23:43:58 +01:00
}
}
}
2018-02-24 01:59:02 +01:00
format = FORMAT_RGBA8 ;
data = result_image ;
2018-02-20 23:43:58 +01:00
}
2014-06-16 15:22:26 +02:00
void Image : : srgb_to_linear ( ) {
2020-05-14 16:41:43 +02:00
if ( data . size ( ) = = 0 ) {
2014-06-16 15:22:26 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2014-06-16 15:22:26 +02:00
2019-10-30 10:39:05 +01:00
static const uint8_t srgb2lin [ 256 ] = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 3 , 3 , 3 , 3 , 3 , 4 , 4 , 4 , 4 , 4 , 5 , 5 , 5 , 5 , 6 , 6 , 6 , 6 , 7 , 7 , 7 , 8 , 8 , 8 , 9 , 9 , 9 , 10 , 10 , 10 , 11 , 11 , 11 , 12 , 12 , 13 , 13 , 13 , 14 , 14 , 15 , 15 , 16 , 16 , 16 , 17 , 17 , 18 , 18 , 19 , 19 , 20 , 20 , 21 , 22 , 22 , 23 , 23 , 24 , 24 , 25 , 26 , 26 , 27 , 27 , 28 , 29 , 29 , 30 , 31 , 31 , 32 , 33 , 33 , 34 , 35 , 36 , 36 , 37 , 38 , 38 , 39 , 40 , 41 , 42 , 42 , 43 , 44 , 45 , 46 , 47 , 47 , 48 , 49 , 50 , 51 , 52 , 53 , 54 , 55 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , 62 , 63 , 64 , 65 , 66 , 67 , 68 , 70 , 71 , 72 , 73 , 74 , 75 , 76 , 77 , 78 , 80 , 81 , 82 , 83 , 84 , 85 , 87 , 88 , 89 , 90 , 92 , 93 , 94 , 95 , 97 , 98 , 99 , 101 , 102 , 103 , 105 , 106 , 107 , 109 , 110 , 112 , 113 , 114 , 116 , 117 , 119 , 120 , 122 , 123 , 125 , 126 , 128 , 129 , 131 , 132 , 134 , 135 , 137 , 139 , 140 , 142 , 144 , 145 , 147 , 148 , 150 , 152 , 153 , 155 , 157 , 159 , 160 , 162 , 164 , 166 , 167 , 169 , 171 , 173 , 175 , 176 , 178 , 180 , 182 , 184 , 186 , 188 , 190 , 192 , 193 , 195 , 197 , 199 , 201 , 203 , 205 , 207 , 209 , 211 , 213 , 215 , 218 , 220 , 222 , 224 , 226 , 228 , 230 , 232 , 235 , 237 , 239 , 241 , 243 , 245 , 248 , 250 , 252 , 255 } ;
2014-06-16 15:22:26 +02:00
2016-10-03 21:33:42 +02:00
ERR_FAIL_COND ( format ! = FORMAT_RGB8 & & format ! = FORMAT_RGBA8 ) ;
2014-06-16 15:22:26 +02:00
2016-10-03 21:33:42 +02:00
if ( format = = FORMAT_RGBA8 ) {
2014-06-16 15:22:26 +02:00
int len = data . size ( ) / 4 ;
2020-02-17 22:06:54 +01:00
uint8_t * data_ptr = data . ptrw ( ) ;
2014-06-16 15:22:26 +02:00
for ( int i = 0 ; i < len ; i + + ) {
data_ptr [ ( i < < 2 ) + 0 ] = srgb2lin [ data_ptr [ ( i < < 2 ) + 0 ] ] ;
data_ptr [ ( i < < 2 ) + 1 ] = srgb2lin [ data_ptr [ ( i < < 2 ) + 1 ] ] ;
data_ptr [ ( i < < 2 ) + 2 ] = srgb2lin [ data_ptr [ ( i < < 2 ) + 2 ] ] ;
}
2016-10-03 21:33:42 +02:00
} else if ( format = = FORMAT_RGB8 ) {
2014-06-16 15:22:26 +02:00
int len = data . size ( ) / 3 ;
2020-02-17 22:06:54 +01:00
uint8_t * data_ptr = data . ptrw ( ) ;
2014-06-16 15:22:26 +02:00
for ( int i = 0 ; i < len ; i + + ) {
data_ptr [ ( i * 3 ) + 0 ] = srgb2lin [ data_ptr [ ( i * 3 ) + 0 ] ] ;
data_ptr [ ( i * 3 ) + 1 ] = srgb2lin [ data_ptr [ ( i * 3 ) + 1 ] ] ;
data_ptr [ ( i * 3 ) + 2 ] = srgb2lin [ data_ptr [ ( i * 3 ) + 2 ] ] ;
}
}
}
2014-05-24 06:35:47 +02:00
void Image : : premultiply_alpha ( ) {
2020-05-14 16:41:43 +02:00
if ( data . size ( ) = = 0 ) {
2014-05-24 06:35:47 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2014-05-24 06:35:47 +02:00
2020-05-14 16:41:43 +02:00
if ( format ! = FORMAT_RGBA8 ) {
2014-05-24 06:35:47 +02:00
return ; //not needed
2020-05-14 16:41:43 +02:00
}
2014-05-24 06:35:47 +02:00
2020-02-17 22:06:54 +01:00
uint8_t * data_ptr = data . ptrw ( ) ;
2014-05-24 06:35:47 +02:00
for ( int i = 0 ; i < height ; i + + ) {
for ( int j = 0 ; j < width ; j + + ) {
uint8_t * ptr = & data_ptr [ ( i * width + j ) * 4 ] ;
2023-05-24 22:00:47 +02:00
ptr [ 0 ] = ( uint16_t ( ptr [ 0 ] ) * uint16_t ( ptr [ 3 ] ) + 255U ) > > 8 ;
ptr [ 1 ] = ( uint16_t ( ptr [ 1 ] ) * uint16_t ( ptr [ 3 ] ) + 255U ) > > 8 ;
ptr [ 2 ] = ( uint16_t ( ptr [ 2 ] ) * uint16_t ( ptr [ 3 ] ) + 255U ) > > 8 ;
2014-05-24 06:35:47 +02:00
}
}
}
2014-02-10 02:10:30 +01:00
void Image : : fix_alpha_edges ( ) {
2020-05-14 16:41:43 +02:00
if ( data . size ( ) = = 0 ) {
2014-02-10 02:10:30 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2020-05-14 16:41:43 +02:00
if ( format ! = FORMAT_RGBA8 ) {
2014-02-10 02:10:30 +01:00
return ; //not needed
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2020-02-17 22:06:54 +01:00
Vector < uint8_t > dcopy = data ;
const uint8_t * srcptr = dcopy . ptr ( ) ;
2014-02-10 02:10:30 +01:00
2020-02-17 22:06:54 +01:00
uint8_t * data_ptr = data . ptrw ( ) ;
2014-02-10 02:10:30 +01:00
const int max_radius = 4 ;
2017-07-08 17:12:18 +02:00
const int alpha_threshold = 20 ;
2014-02-10 02:10:30 +01:00
const int max_dist = 0x7FFFFFFF ;
for ( int i = 0 ; i < height ; i + + ) {
for ( int j = 0 ; j < width ; j + + ) {
2016-10-03 21:33:42 +02:00
const uint8_t * rptr = & srcptr [ ( i * width + j ) * 4 ] ;
uint8_t * wptr = & data_ptr [ ( i * width + j ) * 4 ] ;
2020-05-14 16:41:43 +02:00
if ( rptr [ 3 ] > = alpha_threshold ) {
2014-02-10 02:10:30 +01:00
continue ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
int closest_dist = max_dist ;
2023-10-30 06:50:56 +01:00
uint8_t closest_color [ 3 ] = { 0 } ;
2016-10-03 21:33:42 +02:00
2014-02-10 02:10:30 +01:00
int from_x = MAX ( 0 , j - max_radius ) ;
int to_x = MIN ( width - 1 , j + max_radius ) ;
int from_y = MAX ( 0 , i - max_radius ) ;
int to_y = MIN ( height - 1 , i + max_radius ) ;
2016-10-03 21:33:42 +02:00
2014-02-10 02:10:30 +01:00
for ( int k = from_y ; k < = to_y ; k + + ) {
for ( int l = from_x ; l < = to_x ; l + + ) {
int dy = i - k ;
int dx = j - l ;
int dist = dy * dy + dx * dx ;
2020-05-14 16:41:43 +02:00
if ( dist > = closest_dist ) {
2014-02-10 02:10:30 +01:00
continue ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2019-02-12 21:10:08 +01:00
const uint8_t * rp2 = & srcptr [ ( k * width + l ) < < 2 ] ;
2014-02-10 02:10:30 +01:00
2020-05-14 16:41:43 +02:00
if ( rp2 [ 3 ] < alpha_threshold ) {
2014-02-10 02:10:30 +01:00
continue ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2017-10-13 15:03:05 +02:00
closest_dist = dist ;
2019-02-12 21:10:08 +01:00
closest_color [ 0 ] = rp2 [ 0 ] ;
closest_color [ 1 ] = rp2 [ 1 ] ;
closest_color [ 2 ] = rp2 [ 2 ] ;
2014-02-10 02:10:30 +01:00
}
}
2016-10-03 21:33:42 +02:00
if ( closest_dist ! = max_dist ) {
wptr [ 0 ] = closest_color [ 0 ] ;
wptr [ 1 ] = closest_color [ 1 ] ;
wptr [ 2 ] = closest_color [ 2 ] ;
}
2014-02-10 02:10:30 +01:00
}
}
}
2015-10-21 14:50:44 +02:00
String Image : : get_format_name ( Format p_format ) {
ERR_FAIL_INDEX_V ( p_format , FORMAT_MAX , String ( ) ) ;
return format_names [ p_format ] ;
}
2020-02-17 22:06:54 +01:00
Error Image : : load_png_from_buffer ( const Vector < uint8_t > & p_array ) {
2018-07-14 19:42:37 +02:00
return _load_from_buffer ( p_array , _png_mem_loader_func ) ;
2017-12-20 21:16:21 +01:00
}
2020-02-17 22:06:54 +01:00
Error Image : : load_jpg_from_buffer ( const Vector < uint8_t > & p_array ) {
2018-07-14 19:42:37 +02:00
return _load_from_buffer ( p_array , _jpg_mem_loader_func ) ;
}
2020-02-17 22:06:54 +01:00
Error Image : : load_webp_from_buffer ( const Vector < uint8_t > & p_array ) {
2018-07-14 19:42:37 +02:00
return _load_from_buffer ( p_array , _webp_mem_loader_func ) ;
}
2017-12-20 21:16:21 +01:00
2020-05-21 13:46:44 +02:00
Error Image : : load_tga_from_buffer ( const Vector < uint8_t > & p_array ) {
2020-10-20 23:25:58 +02:00
ERR_FAIL_NULL_V_MSG (
_tga_mem_loader_func ,
ERR_UNAVAILABLE ,
" The TGA module isn't enabled. Recompile the Godot editor or export template binary with the `module_tga_enabled=yes` SCons option. " ) ;
2020-05-21 13:46:44 +02:00
return _load_from_buffer ( p_array , _tga_mem_loader_func ) ;
}
2020-10-20 23:25:58 +02:00
Error Image : : load_bmp_from_buffer ( const Vector < uint8_t > & p_array ) {
ERR_FAIL_NULL_V_MSG (
_bmp_mem_loader_func ,
ERR_UNAVAILABLE ,
" The BMP module isn't enabled. Recompile the Godot editor or export template binary with the `module_bmp_enabled=yes` SCons option. " ) ;
return _load_from_buffer ( p_array , _bmp_mem_loader_func ) ;
}
2023-06-15 01:27:56 +02:00
Error Image : : load_svg_from_buffer ( const Vector < uint8_t > & p_array , float scale ) {
ERR_FAIL_NULL_V_MSG (
_svg_scalable_mem_loader_func ,
ERR_UNAVAILABLE ,
" The SVG module isn't enabled. Recompile the Godot editor or export template binary with the `module_svg_enabled=yes` SCons option. " ) ;
int buffer_size = p_array . size ( ) ;
ERR_FAIL_COND_V ( buffer_size = = 0 , ERR_INVALID_PARAMETER ) ;
Ref < Image > image = _svg_scalable_mem_loader_func ( p_array . ptr ( ) , buffer_size , scale ) ;
ERR_FAIL_COND_V ( ! image . is_valid ( ) , ERR_PARSE_ERROR ) ;
copy_internals_from ( image ) ;
return OK ;
}
Error Image : : load_svg_from_string ( const String & p_svg_str , float scale ) {
return load_svg_from_buffer ( p_svg_str . to_utf8_buffer ( ) , scale ) ;
}
2023-04-28 18:26:01 +02:00
Error Image : : load_ktx_from_buffer ( const Vector < uint8_t > & p_array ) {
ERR_FAIL_NULL_V_MSG (
_ktx_mem_loader_func ,
ERR_UNAVAILABLE ,
" The KTX module isn't enabled. Recompile the Godot editor or export template binary with the `module_ktx_enabled=yes` SCons option. " ) ;
return _load_from_buffer ( p_array , _ktx_mem_loader_func ) ;
}
2019-09-27 04:16:44 +02:00
void Image : : convert_rg_to_ra_rgba8 ( ) {
ERR_FAIL_COND ( format ! = FORMAT_RGBA8 ) ;
ERR_FAIL_COND ( ! data . size ( ) ) ;
int s = data . size ( ) ;
2020-02-17 22:06:54 +01:00
uint8_t * w = data . ptrw ( ) ;
2019-09-27 04:16:44 +02:00
for ( int i = 0 ; i < s ; i + = 4 ) {
w [ i + 3 ] = w [ i + 1 ] ;
w [ i + 1 ] = 0 ;
w [ i + 2 ] = 0 ;
}
}
2020-05-14 14:29:06 +02:00
2019-09-27 04:16:44 +02:00
void Image : : convert_ra_rgba8_to_rg ( ) {
ERR_FAIL_COND ( format ! = FORMAT_RGBA8 ) ;
ERR_FAIL_COND ( ! data . size ( ) ) ;
int s = data . size ( ) ;
2020-02-17 22:06:54 +01:00
uint8_t * w = data . ptrw ( ) ;
2019-09-27 04:16:44 +02:00
for ( int i = 0 ; i < s ; i + = 4 ) {
w [ i + 1 ] = w [ i + 3 ] ;
w [ i + 2 ] = 0 ;
w [ i + 3 ] = 255 ;
}
}
2022-12-01 15:34:05 +01:00
void Image : : convert_rgba8_to_bgra8 ( ) {
ERR_FAIL_COND ( format ! = FORMAT_RGBA8 ) ;
ERR_FAIL_COND ( ! data . size ( ) ) ;
int s = data . size ( ) ;
uint8_t * w = data . ptrw ( ) ;
for ( int i = 0 ; i < s ; i + = 4 ) {
uint8_t r = w [ i ] ;
w [ i ] = w [ i + 2 ] ; // Swap R to B
w [ i + 2 ] = r ; // Swap B to R
}
}
2020-02-17 22:06:54 +01:00
Error Image : : _load_from_buffer ( const Vector < uint8_t > & p_array , ImageMemLoadFunc p_loader ) {
2017-12-20 21:16:21 +01:00
int buffer_size = p_array . size ( ) ;
ERR_FAIL_COND_V ( buffer_size = = 0 , ERR_INVALID_PARAMETER ) ;
2023-09-09 16:11:33 +02:00
ERR_FAIL_NULL_V ( p_loader , ERR_INVALID_PARAMETER ) ;
2017-12-20 21:16:21 +01:00
2020-02-17 22:06:54 +01:00
const uint8_t * r = p_array . ptr ( ) ;
2017-12-20 21:16:21 +01:00
2020-02-17 22:06:54 +01:00
Ref < Image > image = p_loader ( r , buffer_size ) ;
2017-12-20 21:16:21 +01:00
ERR_FAIL_COND_V ( ! image . is_valid ( ) , ERR_PARSE_ERROR ) ;
copy_internals_from ( image ) ;
return OK ;
}
2018-08-22 04:56:04 +02:00
void Image : : average_4_uint8 ( uint8_t & p_out , const uint8_t & p_a , const uint8_t & p_b , const uint8_t & p_c , const uint8_t & p_d ) {
p_out = static_cast < uint8_t > ( ( p_a + p_b + p_c + p_d + 2 ) > > 2 ) ;
}
void Image : : average_4_float ( float & p_out , const float & p_a , const float & p_b , const float & p_c , const float & p_d ) {
p_out = ( p_a + p_b + p_c + p_d ) * 0.25f ;
}
void Image : : average_4_half ( uint16_t & p_out , const uint16_t & p_a , const uint16_t & p_b , const uint16_t & p_c , const uint16_t & p_d ) {
p_out = Math : : make_half_float ( ( Math : : half_to_float ( p_a ) + Math : : half_to_float ( p_b ) + Math : : half_to_float ( p_c ) + Math : : half_to_float ( p_d ) ) * 0.25f ) ;
}
void Image : : average_4_rgbe9995 ( uint32_t & p_out , const uint32_t & p_a , const uint32_t & p_b , const uint32_t & p_c , const uint32_t & p_d ) {
p_out = ( ( Color : : from_rgbe9995 ( p_a ) + Color : : from_rgbe9995 ( p_b ) + Color : : from_rgbe9995 ( p_c ) + Color : : from_rgbe9995 ( p_d ) ) * 0.25f ) . to_rgbe9995 ( ) ;
}
void Image : : renormalize_uint8 ( uint8_t * p_rgb ) {
Vector3 n ( p_rgb [ 0 ] / 255.0 , p_rgb [ 1 ] / 255.0 , p_rgb [ 2 ] / 255.0 ) ;
n * = 2.0 ;
n - = Vector3 ( 1 , 1 , 1 ) ;
n . normalize ( ) ;
n + = Vector3 ( 1 , 1 , 1 ) ;
n * = 0.5 ;
n * = 255 ;
p_rgb [ 0 ] = CLAMP ( int ( n . x ) , 0 , 255 ) ;
p_rgb [ 1 ] = CLAMP ( int ( n . y ) , 0 , 255 ) ;
p_rgb [ 2 ] = CLAMP ( int ( n . z ) , 0 , 255 ) ;
}
void Image : : renormalize_float ( float * p_rgb ) {
Vector3 n ( p_rgb [ 0 ] , p_rgb [ 1 ] , p_rgb [ 2 ] ) ;
n . normalize ( ) ;
p_rgb [ 0 ] = n . x ;
p_rgb [ 1 ] = n . y ;
p_rgb [ 2 ] = n . z ;
}
void Image : : renormalize_half ( uint16_t * p_rgb ) {
Vector3 n ( Math : : half_to_float ( p_rgb [ 0 ] ) , Math : : half_to_float ( p_rgb [ 1 ] ) , Math : : half_to_float ( p_rgb [ 2 ] ) ) ;
n . normalize ( ) ;
p_rgb [ 0 ] = Math : : make_half_float ( n . x ) ;
p_rgb [ 1 ] = Math : : make_half_float ( n . y ) ;
p_rgb [ 2 ] = Math : : make_half_float ( n . z ) ;
}
void Image : : renormalize_rgbe9995 ( uint32_t * p_rgb ) {
// Never used
}
2016-03-12 14:44:12 +01:00
Image : : Image ( const uint8_t * p_mem_png_jpg , int p_len ) {
2014-02-10 02:10:30 +01:00
width = 0 ;
height = 0 ;
2016-10-03 21:33:42 +02:00
mipmaps = false ;
format = FORMAT_L8 ;
2014-02-10 02:10:30 +01:00
if ( _png_mem_loader_func ) {
2017-05-17 12:36:47 +02:00
copy_internals_from ( _png_mem_loader_func ( p_mem_png_jpg , p_len ) ) ;
2016-03-12 14:44:12 +01:00
}
2020-12-15 13:04:21 +01:00
if ( is_empty ( ) & & _jpg_mem_loader_func ) {
2017-05-17 12:36:47 +02:00
copy_internals_from ( _jpg_mem_loader_func ( p_mem_png_jpg , p_len ) ) ;
2014-02-10 02:10:30 +01:00
}
2022-03-24 18:18:55 +01:00
if ( is_empty ( ) & & _webp_mem_loader_func ) {
copy_internals_from ( _webp_mem_loader_func ( p_mem_png_jpg , p_len ) ) ;
}
2014-02-10 02:10:30 +01:00
}
2017-05-17 12:36:47 +02:00
Ref < Resource > Image : : duplicate ( bool p_subresources ) const {
Ref < Image > copy ;
2021-06-18 00:03:09 +02:00
copy . instantiate ( ) ;
2017-05-17 12:36:47 +02:00
copy - > _copy_internals_from ( * this ) ;
return copy ;
}
2020-05-01 14:34:23 +02:00
void Image : : set_as_black ( ) {
2021-04-27 16:19:21 +02:00
memset ( data . ptrw ( ) , 0 , data . size ( ) ) ;
2020-05-01 14:34:23 +02:00
}
2022-01-18 13:39:55 +01:00
Dictionary Image : : compute_image_metrics ( const Ref < Image > p_compared_image , bool p_luma_metric ) {
// https://github.com/richgel999/bc7enc_rdo/blob/master/LICENSE
//
// This is free and unencumbered software released into the public domain.
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
// software, either in source code form or as a compiled binary, for any purpose,
// commercial or non - commercial, and by any means.
// In jurisdictions that recognize copyright laws, the author or authors of this
// software dedicate any and all copyright interest in the software to the public
// domain. We make this dedication for the benefit of the public at large and to
// the detriment of our heirs and successors. We intend this dedication to be an
// overt act of relinquishment in perpetuity of all present and future rights to
// this software under copyright law.
// 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 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.
Dictionary result ;
result [ " max " ] = INFINITY ;
result [ " mean " ] = INFINITY ;
result [ " mean_squared " ] = INFINITY ;
result [ " root_mean_squared " ] = INFINITY ;
result [ " peak_snr " ] = 0.0f ;
ERR_FAIL_NULL_V ( p_compared_image , result ) ;
Error err = OK ;
Ref < Image > compared_image = duplicate ( true ) ;
if ( compared_image - > is_compressed ( ) ) {
err = compared_image - > decompress ( ) ;
}
ERR_FAIL_COND_V ( err ! = OK , result ) ;
Ref < Image > source_image = p_compared_image - > duplicate ( true ) ;
if ( source_image - > is_compressed ( ) ) {
err = source_image - > decompress ( ) ;
}
ERR_FAIL_COND_V ( err ! = OK , result ) ;
ERR_FAIL_COND_V ( err ! = OK , result ) ;
ERR_FAIL_COND_V_MSG ( ( compared_image - > get_format ( ) > = Image : : FORMAT_RH ) & & ( compared_image - > get_format ( ) < = Image : : FORMAT_RGBE9995 ) , result , " Metrics on HDR images are not supported. " ) ;
ERR_FAIL_COND_V_MSG ( ( source_image - > get_format ( ) > = Image : : FORMAT_RH ) & & ( source_image - > get_format ( ) < = Image : : FORMAT_RGBE9995 ) , result , " Metrics on HDR images are not supported. " ) ;
double image_metric_max , image_metric_mean , image_metric_mean_squared , image_metric_root_mean_squared , image_metric_peak_snr = 0.0 ;
const bool average_component_error = true ;
2022-09-29 11:53:28 +02:00
const uint32_t w = MIN ( compared_image - > get_width ( ) , source_image - > get_width ( ) ) ;
const uint32_t h = MIN ( compared_image - > get_height ( ) , source_image - > get_height ( ) ) ;
2022-01-18 13:39:55 +01:00
// Histogram approach originally due to Charles Bloom.
double hist [ 256 ] ;
memset ( hist , 0 , sizeof ( hist ) ) ;
2022-09-29 11:53:28 +02:00
for ( uint32_t y = 0 ; y < h ; y + + ) {
for ( uint32_t x = 0 ; x < w ; x + + ) {
2022-01-18 13:39:55 +01:00
const Color color_a = compared_image - > get_pixel ( x , y ) ;
const Color color_b = source_image - > get_pixel ( x , y ) ;
if ( ! p_luma_metric ) {
ERR_FAIL_COND_V_MSG ( color_a . r > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
ERR_FAIL_COND_V_MSG ( color_b . r > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
hist [ Math : : abs ( color_a . get_r8 ( ) - color_b . get_r8 ( ) ) ] + + ;
ERR_FAIL_COND_V_MSG ( color_a . g > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
ERR_FAIL_COND_V_MSG ( color_b . g > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
hist [ Math : : abs ( color_a . get_g8 ( ) - color_b . get_g8 ( ) ) ] + + ;
ERR_FAIL_COND_V_MSG ( color_a . b > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
ERR_FAIL_COND_V_MSG ( color_b . b > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
hist [ Math : : abs ( color_a . get_b8 ( ) - color_b . get_b8 ( ) ) ] + + ;
ERR_FAIL_COND_V_MSG ( color_a . a > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
ERR_FAIL_COND_V_MSG ( color_b . a > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
hist [ Math : : abs ( color_a . get_a8 ( ) - color_b . get_a8 ( ) ) ] + + ;
} else {
ERR_FAIL_COND_V_MSG ( color_a . r > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
ERR_FAIL_COND_V_MSG ( color_b . r > 1.0f , Dictionary ( ) , " Can't compare HDR colors. " ) ;
// REC709 weightings
int luma_a = ( 13938U * color_a . get_r8 ( ) + 46869U * color_a . get_g8 ( ) + 4729U * color_a . get_b8 ( ) + 32768U ) > > 16U ;
int luma_b = ( 13938U * color_b . get_r8 ( ) + 46869U * color_b . get_g8 ( ) + 4729U * color_b . get_b8 ( ) + 32768U ) > > 16U ;
hist [ Math : : abs ( luma_a - luma_b ) ] + + ;
}
}
}
image_metric_max = 0 ;
double sum = 0.0f , sum2 = 0.0f ;
for ( uint32_t i = 0 ; i < 256 ; i + + ) {
if ( ! hist [ i ] ) {
continue ;
}
image_metric_max = MAX ( image_metric_max , i ) ;
double x = i * hist [ i ] ;
sum + = x ;
sum2 + = i * x ;
}
// See http://richg42.blogspot.com/2016/09/how-to-compute-psnr-from-old-berkeley.html
2022-09-29 11:53:28 +02:00
double total_values = w * h ;
2022-01-18 13:39:55 +01:00
if ( average_component_error ) {
total_values * = 4 ;
}
image_metric_mean = CLAMP ( sum / total_values , 0.0f , 255.0f ) ;
image_metric_mean_squared = CLAMP ( sum2 / total_values , 0.0f , 255.0f * 255.0f ) ;
image_metric_root_mean_squared = sqrt ( image_metric_mean_squared ) ;
if ( ! image_metric_root_mean_squared ) {
image_metric_peak_snr = 1e+10 f ;
} else {
image_metric_peak_snr = CLAMP ( log10 ( 255.0f / image_metric_root_mean_squared ) * 20.0f , 0.0f , 500.0f ) ;
}
result [ " max " ] = image_metric_max ;
result [ " mean " ] = image_metric_mean ;
result [ " mean_squared " ] = image_metric_mean_squared ;
result [ " root_mean_squared " ] = image_metric_root_mean_squared ;
result [ " peak_snr " ] = image_metric_peak_snr ;
return result ;
}