2014-02-10 01:10:30 +00:00
/*************************************************************************/
/* image.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
2017-08-27 12:16:55 +00:00
/* https://godotengine.org */
2014-02-10 01:10:30 +00:00
/*************************************************************************/
2019-01-01 11:53:14 +00:00
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
2014-02-10 01:10:30 +00:00
/* */
/* 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-04 23:50:27 +00:00
2014-02-10 01:10:30 +00:00
# include "image.h"
2017-01-16 07:04:19 +00:00
2018-09-11 16:13:45 +00:00
# include "core/hash_map.h"
2014-02-10 01:10:30 +00:00
# include "core/io/image_loader.h"
2018-09-11 16:13:45 +00:00
# include "core/io/resource_loader.h"
# include "core/math/math_funcs.h"
2014-02-10 01:10:30 +00:00
# include "core/os/copymem.h"
2018-09-11 16:13:45 +00:00
# include "core/print_string.h"
2017-01-16 07:04:19 +00:00
2017-04-28 16:29:15 +00:00
# include "thirdparty/misc/hq2x.h"
2018-09-11 16:13:45 +00:00
2014-02-10 01:10:30 +00:00
# include <stdio.h>
2017-03-05 15:44:50 +00:00
const char * Image : : format_names [ Image : : FORMAT_MAX ] = {
2016-10-03 19:33:42 +00: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 00:49:49 +00:00
" RGBE9995 " ,
" DXT1 RGB8 " , //s3tc
" DXT3 RGBA8 " ,
" DXT5 RGBA8 " ,
" RGTC Red8 " ,
" RGTC RedGreen8 " ,
2016-10-03 19:33:42 +00:00
" BPTC_RGBA " ,
" BPTC_RGBF " ,
" BPTC_RGBFU " ,
" PVRTC2 " , //pvrtc
" PVRTC2A " ,
2015-10-21 12:50:44 +00:00
" PVRTC4 " ,
2016-10-03 19:33:42 +00:00
" PVRTC4A " ,
" ETC " , //etc1
" ETC2_R11 " , //etc2
" ETC2_R11S " , //signed", NOT srgb.
" ETC2_RG11 " ,
" ETC2_RG11S " ,
" ETC2_RGB8 " ,
" ETC2_RGBA8 " ,
" ETC2_RGB8A1 " ,
2015-10-21 12:50:44 +00:00
} ;
2014-11-13 03:53:12 +00:00
SavePNGFunc Image : : save_png_func = NULL ;
2019-08-03 00:07:13 +00:00
SaveEXRFunc Image : : save_exr_func = NULL ;
2014-02-10 01:10:30 +00:00
2017-06-09 03:23:50 +00:00
void Image : : _put_pixelb ( int p_x , int p_y , uint32_t p_pixelsize , uint8_t * p_data , const uint8_t * p_pixel ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
uint32_t ofs = ( p_y * width + p_x ) * p_pixelsize ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( uint32_t i = 0 ; i < p_pixelsize ; i + + ) {
2017-06-09 03:23:50 +00:00
p_data [ ofs + i ] = p_pixel [ i ] ;
2016-10-03 19:33:42 +00:00
}
2014-02-10 01:10:30 +00:00
}
2017-06-09 03:23:50 +00:00
void Image : : _get_pixelb ( int p_x , int p_y , uint32_t p_pixelsize , const uint8_t * p_data , uint8_t * p_pixel ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
uint32_t ofs = ( p_y * width + p_x ) * p_pixelsize ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( uint32_t i = 0 ; i < p_pixelsize ; i + + ) {
2017-06-09 03:23:50 +00:00
p_pixel [ i ] = p_data [ ofs + i ] ;
2016-10-03 19:33:42 +00:00
}
}
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
int Image : : get_format_pixel_size ( Format p_format ) {
2017-03-05 15:44:50 +00:00
switch ( p_format ) {
case FORMAT_L8 :
return 1 ; //luminance
case FORMAT_LA8 :
return 2 ; //luminance-alpha
2016-10-03 19:33:42 +00: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_RGBA5551 : return 2 ;
2017-03-05 15:44:50 +00:00
case FORMAT_RF :
return 4 ; //float
2016-10-03 19:33:42 +00:00
case FORMAT_RGF : return 8 ;
case FORMAT_RGBF : return 12 ;
case FORMAT_RGBAF : return 16 ;
2017-03-05 15:44:50 +00:00
case FORMAT_RH :
return 2 ; //half float
2016-10-03 19:33:42 +00:00
case FORMAT_RGH : return 4 ;
2017-05-27 00:49:49 +00:00
case FORMAT_RGBH : return 6 ;
case FORMAT_RGBAH : return 8 ;
case FORMAT_RGBE9995 : return 4 ;
2017-03-05 15:44:50 +00:00
case FORMAT_DXT1 :
return 1 ; //s3tc bc1
case FORMAT_DXT3 :
return 1 ; //bc2
case FORMAT_DXT5 :
return 1 ; //bc3
2017-05-27 00:49:49 +00:00
case FORMAT_RGTC_R :
2017-03-05 15:44:50 +00:00
return 1 ; //bc4
2017-05-27 00:49:49 +00:00
case FORMAT_RGTC_RG :
2017-03-05 15:44:50 +00: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_PVRTC2 :
return 1 ; //pvrtc
2016-10-03 19:33:42 +00:00
case FORMAT_PVRTC2A : return 1 ;
case FORMAT_PVRTC4 : return 1 ;
case FORMAT_PVRTC4A : return 1 ;
2017-03-05 15:44:50 +00:00
case FORMAT_ETC :
return 1 ; //etc1
case FORMAT_ETC2_R11 :
return 1 ; //etc2
case FORMAT_ETC2_R11S :
return 1 ; //signed: return 1; NOT srgb.
2016-10-03 19:33:42 +00: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 ;
2017-03-05 15:44:50 +00:00
case FORMAT_MAX : {
}
2016-10-03 19:33:42 +00:00
}
return 0 ;
}
2017-03-05 15:44:50 +00:00
void Image : : get_format_min_pixel_size ( Format p_format , int & r_w , int & r_h ) {
2016-10-03 19:33:42 +00:00
2017-03-05 15:44:50 +00:00
switch ( p_format ) {
2016-10-03 19:33:42 +00:00
case FORMAT_DXT1 : //s3tc bc1
case FORMAT_DXT3 : //bc2
case FORMAT_DXT5 : //bc3
2017-05-27 00:49:49 +00:00
case FORMAT_RGTC_R : //bc4
case FORMAT_RGTC_RG : { //bc5 case case FORMAT_DXT1:
2016-10-03 19:33:42 +00:00
2017-03-05 15:44:50 +00:00
r_w = 4 ;
r_h = 4 ;
2014-02-10 01:10:30 +00:00
} break ;
2016-10-03 19:33:42 +00:00
case FORMAT_PVRTC2 :
case FORMAT_PVRTC2A : {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
r_w = 16 ;
r_h = 8 ;
2016-10-03 19:33:42 +00:00
} break ;
case FORMAT_PVRTC4A :
case FORMAT_PVRTC4 : {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
r_w = 8 ;
r_h = 8 ;
2014-02-10 01:10:30 +00:00
} break ;
2016-10-03 19:33:42 +00:00
case FORMAT_ETC : {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
r_w = 4 ;
r_h = 4 ;
2016-10-03 19:33:42 +00:00
} break ;
case FORMAT_BPTC_RGBA :
case FORMAT_BPTC_RGBF :
case FORMAT_BPTC_RGBFU : {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
r_w = 4 ;
r_h = 4 ;
2014-02-10 01:10:30 +00:00
} break ;
2017-03-05 15:44:50 +00:00
case FORMAT_ETC2_R11 : //etc2
2016-10-03 19:33:42 +00:00
case FORMAT_ETC2_R11S : //signed: NOT srgb.
case FORMAT_ETC2_RG11 :
case FORMAT_ETC2_RG11S :
case FORMAT_ETC2_RGB8 :
case FORMAT_ETC2_RGBA8 :
case FORMAT_ETC2_RGB8A1 : {
2017-03-05 15:44:50 +00:00
r_w = 4 ;
r_h = 4 ;
2014-02-10 01:10:30 +00:00
} break ;
2016-10-03 19:33:42 +00:00
default : {
2017-03-05 15:44:50 +00:00
r_w = 1 ;
r_h = 1 ;
2016-10-03 19:33:42 +00:00
} break ;
2014-02-10 01:10:30 +00:00
}
}
2016-10-03 19:33:42 +00:00
int Image : : get_format_pixel_rshift ( Format p_format ) {
2017-05-31 23:16:38 +00:00
if ( p_format = = FORMAT_DXT1 | | p_format = = FORMAT_RGTC_R | | p_format = = FORMAT_PVRTC4 | | p_format = = FORMAT_PVRTC4A | | 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 19:33:42 +00:00
return 1 ;
2017-03-05 15:44:50 +00:00
else if ( p_format = = FORMAT_PVRTC2 | | p_format = = FORMAT_PVRTC2A )
2016-10-03 19:33:42 +00:00
return 2 ;
else
return 0 ;
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_PVRTC2 :
case FORMAT_PVRTC2A : {
return 4 ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_PVRTC4A :
case FORMAT_PVRTC4 : {
return 4 ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_ETC : {
return 4 ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_BPTC_RGBA :
case FORMAT_BPTC_RGBF :
case FORMAT_BPTC_RGBFU : {
return 4 ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 :
case FORMAT_ETC2_RGB8A1 : {
return 4 ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
default : {
}
}
return 1 ;
}
2017-03-05 15:44:50 +00:00
void Image : : _get_mipmap_offset_and_size ( int p_mipmap , int & r_offset , int & r_width , int & r_height ) const {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
int w = width ;
int h = height ;
int ofs = 0 ;
2014-02-10 01:10:30 +00:00
int pixel_size = get_format_pixel_size ( format ) ;
int pixel_rshift = get_format_pixel_rshift ( format ) ;
2017-05-27 00:49:49 +00:00
int block = get_format_block_size ( format ) ;
2017-03-05 15:44:50 +00:00
int minw , minh ;
get_format_min_pixel_size ( format , minw , minh ) ;
for ( int i = 0 ; i < p_mipmap ; i + + ) {
2017-05-27 00:49:49 +00: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 ;
2017-03-05 15:44:50 +00:00
s * = pixel_size ;
s > > = pixel_rshift ;
ofs + = s ;
w = MAX ( minw , w > > 1 ) ;
h = MAX ( minh , h > > 1 ) ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
r_offset = ofs ;
r_width = w ;
r_height = h ;
2014-02-10 01:10:30 +00:00
}
2018-10-03 15:27:22 +00:00
2014-02-10 01:10:30 +00:00
int Image : : get_mipmap_offset ( int p_mipmap ) const {
2017-03-05 15:44:50 +00:00
ERR_FAIL_INDEX_V ( p_mipmap , get_mipmap_count ( ) + 1 , - 1 ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
int ofs , w , h ;
_get_mipmap_offset_and_size ( p_mipmap , ofs , w , h ) ;
2014-02-10 01:10:30 +00:00
return ofs ;
}
2017-03-05 15:44:50 +00:00
void Image : : get_mipmap_offset_and_size ( int p_mipmap , int & r_ofs , int & r_size ) const {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
int ofs , w , h ;
_get_mipmap_offset_and_size ( p_mipmap , ofs , w , h ) ;
2014-02-10 01:10:30 +00:00
int ofs2 ;
2017-03-05 15:44:50 +00:00
_get_mipmap_offset_and_size ( p_mipmap + 1 , ofs2 , w , h ) ;
r_ofs = ofs ;
r_size = ofs2 - ofs ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
void Image : : get_mipmap_offset_size_and_dimensions ( int p_mipmap , int & r_ofs , int & r_size , int & w , int & h ) const {
2015-04-07 00:48:20 +00:00
int ofs ;
2017-03-05 15:44:50 +00:00
_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 ;
2015-04-07 00:48:20 +00:00
}
2016-10-03 19:33:42 +00:00
int Image : : get_width ( ) const {
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
return width ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
int Image : : get_height ( ) const {
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
return height ;
2014-02-10 01:10:30 +00:00
}
2017-08-26 11:45:03 +00:00
Vector2 Image : : get_size ( ) const {
return Vector2 ( width , height ) ;
}
2016-10-03 19:33:42 +00:00
bool Image : : has_mipmaps ( ) const {
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
return mipmaps ;
2014-02-10 01:10:30 +00:00
}
2016-10-03 19:33:42 +00:00
int Image : : get_mipmap_count ( ) const {
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
if ( mipmaps )
2017-03-05 15:44:50 +00:00
return get_image_required_mipmaps ( width , height , format ) ;
2016-10-03 19:33:42 +00:00
else
return 0 ;
}
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
//using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers
2017-03-05 15:44:50 +00:00
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 ) {
2014-02-10 01:10:30 +00:00
2018-04-21 14:35:23 +00:00
uint32_t max_bytes = MAX ( read_bytes , write_bytes ) ;
2017-03-05 15:44:50 +00:00
for ( int y = 0 ; y < p_height ; y + + ) {
for ( int x = 0 ; x < p_width ; x + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
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 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
uint8_t rgba [ 4 ] ;
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
if ( read_gray ) {
2017-03-05 15:44:50 +00:00
rgba [ 0 ] = rofs [ 0 ] ;
rgba [ 1 ] = rofs [ 0 ] ;
rgba [ 2 ] = rofs [ 0 ] ;
2016-10-03 19:33:42 +00:00
} else {
2018-04-21 14:35:23 +00:00
for ( uint32_t i = 0 ; i < max_bytes ; i + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
rgba [ i ] = ( i < read_bytes ) ? rofs [ i ] : 0 ;
2016-10-03 19:33:42 +00:00
}
}
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
if ( read_alpha | | write_alpha ) {
2017-03-05 15:44:50 +00:00
rgba [ 3 ] = read_alpha ? rofs [ read_bytes ] : 255 ;
2016-10-03 19:33:42 +00:00
}
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
if ( write_gray ) {
//TODO: not correct grayscale, should use fixed point version of actual weights
2017-03-05 15:44:50 +00:00
wofs [ 0 ] = uint8_t ( ( uint16_t ( rofs [ 0 ] ) + uint16_t ( rofs [ 1 ] ) + uint16_t ( rofs [ 2 ] ) ) / 3 ) ;
2016-10-03 19:33:42 +00:00
} else {
2017-03-05 15:44:50 +00:00
for ( uint32_t i = 0 ; i < write_bytes ; i + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
wofs [ i ] = rgba [ i ] ;
2016-10-03 19:33:42 +00:00
}
}
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
if ( write_alpha ) {
2017-03-05 15:44:50 +00:00
wofs [ write_bytes ] = rgba [ 3 ] ;
2016-10-03 19:33:42 +00:00
}
}
}
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
void Image : : convert ( Format p_new_format ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
if ( data . size ( ) = = 0 )
2014-02-10 01:10:30 +00:00
return ;
2017-03-05 15:44:50 +00:00
if ( p_new_format = = format )
2014-02-10 01:10:30 +00:00
return ;
2017-05-27 00:49:49 +00:00
if ( format > FORMAT_RGBE9995 | | p_new_format > FORMAT_RGBE9995 ) {
2014-02-10 01:10:30 +00:00
2019-08-15 02:57:49 +00:00
ERR_FAIL_MSG ( " Cannot convert to <-> from compressed formats. Use compress() and decompress() instead. " ) ;
2017-05-27 00:49:49 +00:00
} else if ( format > FORMAT_RGBA8 | | p_new_format > FORMAT_RGBA8 ) {
//use put/set pixel which is slower but works with non byte formats
Image new_img ( width , height , 0 , p_new_format ) ;
lock ( ) ;
new_img . lock ( ) ;
for ( int i = 0 ; i < width ; i + + ) {
for ( int j = 0 ; j < height ; j + + ) {
2017-07-07 17:05:45 +00:00
new_img . set_pixel ( i , j , get_pixel ( i , j ) ) ;
2017-05-27 00:49:49 +00:00
}
}
unlock ( ) ;
new_img . unlock ( ) ;
if ( has_mipmaps ( ) ) {
new_img . generate_mipmaps ( ) ;
}
_copy_internals_from ( new_img ) ;
return ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
Image new_img ( width , height , 0 , p_new_format ) ;
2016-03-08 23:00:52 +00:00
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Read r = data . read ( ) ;
PoolVector < uint8_t > : : Write w = new_img . data . write ( ) ;
2014-02-10 01:10:30 +00:00
const uint8_t * rptr = r . ptr ( ) ;
uint8_t * wptr = w . ptr ( ) ;
2017-03-05 15:44:50 +00:00
int conversion_type = format | p_new_format < < 8 ;
switch ( conversion_type ) {
case FORMAT_L8 | ( FORMAT_LA8 < < 8 ) : _convert < 1 , false , 1 , true , true , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_L8 | ( FORMAT_R8 < < 8 ) : _convert < 1 , false , 1 , false , true , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_L8 | ( FORMAT_RG8 < < 8 ) : _convert < 1 , false , 2 , false , true , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_L8 | ( FORMAT_RGB8 < < 8 ) : _convert < 1 , false , 3 , false , true , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_L8 | ( FORMAT_RGBA8 < < 8 ) : _convert < 1 , false , 3 , true , true , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_LA8 | ( FORMAT_L8 < < 8 ) : _convert < 1 , true , 1 , false , true , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_LA8 | ( FORMAT_R8 < < 8 ) : _convert < 1 , true , 1 , false , true , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_LA8 | ( FORMAT_RG8 < < 8 ) : _convert < 1 , true , 2 , false , true , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_LA8 | ( FORMAT_RGB8 < < 8 ) : _convert < 1 , true , 3 , false , true , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_LA8 | ( FORMAT_RGBA8 < < 8 ) : _convert < 1 , true , 3 , true , true , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_R8 | ( FORMAT_L8 < < 8 ) : _convert < 1 , false , 1 , false , false , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_R8 | ( FORMAT_LA8 < < 8 ) : _convert < 1 , false , 1 , true , false , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_R8 | ( FORMAT_RG8 < < 8 ) : _convert < 1 , false , 2 , false , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_R8 | ( FORMAT_RGB8 < < 8 ) : _convert < 1 , false , 3 , false , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_R8 | ( FORMAT_RGBA8 < < 8 ) : _convert < 1 , false , 3 , true , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RG8 | ( FORMAT_L8 < < 8 ) : _convert < 2 , false , 1 , false , false , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RG8 | ( FORMAT_LA8 < < 8 ) : _convert < 2 , false , 1 , true , false , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RG8 | ( FORMAT_R8 < < 8 ) : _convert < 2 , false , 1 , false , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RG8 | ( FORMAT_RGB8 < < 8 ) : _convert < 2 , false , 3 , false , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RG8 | ( FORMAT_RGBA8 < < 8 ) : _convert < 2 , false , 3 , true , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGB8 | ( FORMAT_L8 < < 8 ) : _convert < 3 , false , 1 , false , false , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGB8 | ( FORMAT_LA8 < < 8 ) : _convert < 3 , false , 1 , true , false , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGB8 | ( FORMAT_R8 < < 8 ) : _convert < 3 , false , 1 , false , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGB8 | ( FORMAT_RG8 < < 8 ) : _convert < 3 , false , 2 , false , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGB8 | ( FORMAT_RGBA8 < < 8 ) : _convert < 3 , false , 3 , true , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGBA8 | ( FORMAT_L8 < < 8 ) : _convert < 3 , true , 1 , false , false , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGBA8 | ( FORMAT_LA8 < < 8 ) : _convert < 3 , true , 1 , true , false , true > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGBA8 | ( FORMAT_R8 < < 8 ) : _convert < 3 , true , 1 , false , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGBA8 | ( FORMAT_RG8 < < 8 ) : _convert < 3 , true , 2 , false , false , false > ( width , height , rptr , wptr ) ; break ;
case FORMAT_RGBA8 | ( FORMAT_RGB8 < < 8 ) : _convert < 3 , true , 3 , false , false , false > ( width , height , rptr , wptr ) ; break ;
2014-02-10 01:10:30 +00:00
}
2019-07-05 17:08:43 +00:00
r . release ( ) ;
w . release ( ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
bool gen_mipmaps = mipmaps ;
2016-10-03 19:33:42 +00:00
2017-05-17 10:36:47 +00:00
_copy_internals_from ( new_img ) ;
2014-02-10 01:10:30 +00:00
if ( gen_mipmaps )
generate_mipmaps ( ) ;
}
2017-03-05 15:44:50 +00:00
Image : : Format Image : : get_format ( ) const {
2014-02-10 01:10:30 +00:00
return format ;
}
2017-03-05 15:44:50 +00:00
static double _bicubic_interp_kernel ( double x ) {
2015-10-01 19:25:36 +00:00
x = ABS ( x ) ;
double bc = 0 ;
2017-03-05 15:44:50 +00:00
if ( x < = 1 )
bc = ( 1.5 * x - 2.5 ) * x * x + 1 ;
else if ( x < 2 )
bc = ( ( - 0.5 * x + 2.5 ) * x - 4 ) * x + 2 ;
2015-10-01 19:25:36 +00:00
return bc ;
}
2018-08-22 20:56:47 +00:00
template < int CC , class T >
2018-06-01 19:56:55 +00: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 19:25:36 +00:00
// get source image size
2017-03-05 15:44:50 +00:00
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 03:58:29 +00:00
// coordinates of source points and coefficients
2017-03-05 15:44:50 +00:00
double ox , oy , dx , dy , k1 , k2 ;
int ox1 , oy1 , ox2 , oy2 ;
2015-10-01 19:25:36 +00:00
// destination pixel values
// width and height decreased by 1
int ymax = height - 1 ;
int xmax = width - 1 ;
// temporary pointer
2017-03-05 15:44:50 +00:00
for ( uint32_t y = 0 ; y < p_dst_height ; y + + ) {
2015-10-01 19:25:36 +00:00
// Y coordinates
2017-03-05 15:44:50 +00:00
oy = ( double ) y * yfac - 0.5f ;
oy1 = ( int ) oy ;
dy = oy - ( double ) oy1 ;
2015-10-01 19:25:36 +00:00
2017-03-05 15:44:50 +00:00
for ( uint32_t x = 0 ; x < p_dst_width ; x + + ) {
2015-10-01 19:25:36 +00:00
// X coordinates
2017-03-05 15:44:50 +00:00
ox = ( double ) x * xfac - 0.5f ;
ox1 = ( int ) ox ;
dx = ox - ( double ) ox1 ;
2015-10-01 19:25:36 +00:00
// initial pixel value
2018-08-22 19:40:43 +00:00
T * __restrict dst = ( ( T * ) p_dst ) + ( y * p_dst_width + x ) * CC ;
2015-10-01 19:25:36 +00:00
double color [ CC ] ;
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < CC ; i + + ) {
color [ i ] = 0 ;
2015-10-01 19:25:36 +00:00
}
2017-03-05 15:44:50 +00:00
for ( int n = - 1 ; n < 3 ; n + + ) {
2017-09-22 03:58:29 +00:00
// get Y coefficient
2017-03-05 15:44:50 +00:00
k1 = _bicubic_interp_kernel ( dy - ( double ) n ) ;
2015-10-01 19:25:36 +00:00
oy2 = oy1 + n ;
2017-03-05 15:44:50 +00:00
if ( oy2 < 0 )
2015-10-01 19:25:36 +00:00
oy2 = 0 ;
2017-03-05 15:44:50 +00:00
if ( oy2 > ymax )
2015-10-01 19:25:36 +00:00
oy2 = ymax ;
2017-03-05 15:44:50 +00:00
for ( int m = - 1 ; m < 3 ; m + + ) {
2017-09-22 03:58:29 +00:00
// get X coefficient
2017-03-05 15:44:50 +00:00
k2 = k1 * _bicubic_interp_kernel ( ( double ) m - dx ) ;
2015-10-01 19:25:36 +00:00
ox2 = ox1 + m ;
2017-03-05 15:44:50 +00:00
if ( ox2 < 0 )
2015-10-01 19:25:36 +00:00
ox2 = 0 ;
2017-03-05 15:44:50 +00:00
if ( ox2 > xmax )
2015-10-01 19:25:36 +00:00
ox2 = xmax ;
// get pixel of original image
2018-08-22 19:40:43 +00:00
const T * __restrict p = ( ( T * ) p_src ) + ( oy2 * p_src_width + ox2 ) * CC ;
2015-10-01 19:25:36 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < CC ; i + + ) {
2018-08-22 19:40:43 +00:00
if ( sizeof ( T ) = = 2 ) { //half float
color [ i ] = Math : : half_to_float ( p [ i ] ) ;
} else {
color [ i ] + = p [ i ] * k2 ;
}
2015-10-01 19:25:36 +00:00
}
}
}
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < CC ; i + + ) {
2018-08-22 19:40:43 +00:00
if ( sizeof ( T ) = = 1 ) { //byte
dst [ i ] = CLAMP ( Math : : fast_ftoi ( color [ i ] ) , 0 , 255 ) ;
} else if ( sizeof ( T ) = = 2 ) { //half float
dst [ i ] = Math : : make_half_float ( color [ i ] ) ;
} else {
dst [ i ] = color [ i ] ;
}
2015-10-01 19:25:36 +00:00
}
}
}
}
2018-08-22 20:56:47 +00:00
template < int CC , class T >
2018-06-01 19:56:55 +00: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 01:10:30 +00:00
enum {
2017-03-05 15:44:50 +00:00
FRAC_BITS = 8 ,
FRAC_LEN = ( 1 < < FRAC_BITS ) ,
FRAC_MASK = FRAC_LEN - 1
2014-02-10 01:10:30 +00:00
} ;
2017-03-05 15:44:50 +00:00
for ( uint32_t i = 0 ; i < p_dst_height ; i + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
uint32_t src_yofs_up_fp = ( i * p_src_height * FRAC_LEN / p_dst_height ) ;
2014-02-10 01:10:30 +00:00
uint32_t src_yofs_frac = src_yofs_up_fp & FRAC_MASK ;
uint32_t src_yofs_up = src_yofs_up_fp > > FRAC_BITS ;
2017-03-05 15:44:50 +00:00
uint32_t src_yofs_down = ( i + 1 ) * p_src_height / p_dst_height ;
if ( src_yofs_down > = p_src_height )
src_yofs_down = p_src_height - 1 ;
2014-02-10 01:10:30 +00:00
//src_yofs_up*=CC;
//src_yofs_down*=CC;
uint32_t y_ofs_up = src_yofs_up * p_src_width * CC ;
uint32_t y_ofs_down = src_yofs_down * p_src_width * CC ;
2017-03-05 15:44:50 +00:00
for ( uint32_t j = 0 ; j < p_dst_width ; j + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
uint32_t src_xofs_left_fp = ( j * p_src_width * FRAC_LEN / p_dst_width ) ;
2014-02-10 01:10:30 +00:00
uint32_t src_xofs_frac = src_xofs_left_fp & FRAC_MASK ;
uint32_t src_xofs_left = src_xofs_left_fp > > FRAC_BITS ;
2017-03-05 15:44:50 +00:00
uint32_t src_xofs_right = ( j + 1 ) * p_src_width / p_dst_width ;
if ( src_xofs_right > = p_src_width )
src_xofs_right = p_src_width - 1 ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
src_xofs_left * = CC ;
src_xofs_right * = CC ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( uint32_t l = 0 ; l < CC ; l + + ) {
2014-02-10 01:10:30 +00:00
2018-08-22 19:40:43 +00:00
if ( sizeof ( T ) = = 1 ) { //uint8
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 ;
p_dst [ i * p_dst_width * CC + j * CC + l ] = interp ;
} else if ( sizeof ( T ) = = 2 ) { //half float
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 ) ;
} else if ( sizeof ( T ) = = 4 ) { //float
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 01:10:30 +00:00
}
}
}
}
2018-08-22 20:56:47 +00:00
template < int CC , class T >
2018-06-01 19:56:55 +00: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 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( uint32_t i = 0 ; i < p_dst_height ; i + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
uint32_t src_yofs = i * p_src_height / p_dst_height ;
2014-02-10 01:10:30 +00:00
uint32_t y_ofs = src_yofs * p_src_width * CC ;
2017-03-05 15:44:50 +00:00
for ( uint32_t j = 0 ; j < p_dst_width ; j + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
uint32_t src_xofs = j * p_src_width / p_dst_width ;
src_xofs * = CC ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( uint32_t l = 0 ; l < CC ; l + + ) {
2014-02-10 01:10:30 +00:00
2018-08-22 19:40:43 +00: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 01:10:30 +00:00
}
}
}
}
2019-05-05 12:03:52 +00: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 11:22:22 +00:00
float * kernel = memnew_arr ( float , half_kernel * 2 ) ;
2019-05-05 12:03:52 +00:00
for ( int32_t buffer_x = 0 ; buffer_x < dst_width ; buffer_x + + ) {
2019-08-14 23:35:23 +00: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 12:03:52 +00:00
// Create the kernel used by all the pixels of the column
for ( int32_t target_x = start_x ; target_x < = end_x ; target_x + + )
2019-08-14 23:35:23 +00:00
kernel [ target_x - start_x ] = _lanczos ( ( target_x + 0.5f - src_x ) / scale_factor ) ;
2019-05-05 12:03:52 +00: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 + + ) {
if ( sizeof ( T ) = = 2 ) //half float
pixel [ i ] + = Math : : half_to_float ( src_data [ i ] ) * lanczos_val ;
else
pixel [ i ] + = src_data [ i ] * lanczos_val ;
}
}
float * dst_data = ( ( float * ) buffer ) + ( buffer_y * dst_width + buffer_x ) * CC ;
for ( uint32_t i = 0 ; i < CC ; i + + )
dst_data [ i ] = pixel [ i ] / weight ; // Normalize the sum of all the samples
}
}
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 11:22:22 +00:00
float * kernel = memnew_arr ( float , half_kernel * 2 ) ;
2019-05-05 12:03:52 +00:00
for ( int32_t dst_y = 0 ; dst_y < dst_height ; dst_y + + ) {
2019-08-14 23:35:23 +00: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 12:03:52 +00:00
for ( int32_t target_y = start_y ; target_y < = end_y ; target_y + + )
2019-08-14 23:35:23 +00:00
kernel [ target_y - start_y ] = _lanczos ( ( target_y + 0.5f - buffer_y ) / scale_factor ) ;
2019-05-05 12:03:52 +00: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 ;
for ( uint32_t i = 0 ; i < CC ; i + + )
pixel [ i ] + = buffer_data [ i ] * lanczos_val ;
}
T * dst_data = ( ( T * ) p_dst ) + ( dst_y * dst_width + dst_x ) * CC ;
for ( uint32_t i = 0 ; i < CC ; i + + ) {
pixel [ i ] / = weight ;
if ( sizeof ( T ) = = 1 ) //byte
dst_data [ i ] = CLAMP ( Math : : fast_ftoi ( pixel [ i ] ) , 0 , 255 ) ;
else if ( sizeof ( T ) = = 2 ) //half float
dst_data [ i ] = Math : : make_half_float ( pixel [ i ] ) ;
else // float
dst_data [ i ] = pixel [ i ] ;
}
}
}
memdelete_arr ( kernel ) ;
} // End of second pass
memdelete_arr ( buffer ) ;
}
2018-06-01 19:56:55 +00: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 ) {
2018-06-01 19:53:35 +00:00
2019-07-20 06:09:57 +00:00
uint16_t alpha = MIN ( ( uint16_t ) ( p_alpha * 256.0f ) , 256 ) ;
2018-06-01 19:53:35 +00: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 15:15:10 +00:00
bool Image : : is_size_po2 ( ) const {
return uint32_t ( width ) = = next_power_of_2 ( width ) & & uint32_t ( height ) = = next_power_of_2 ( height ) ;
}
2014-02-10 01:10:30 +00:00
void Image : : resize_to_po2 ( bool p_square ) {
2019-08-26 15:21:19 +00:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot resize in compressed or custom image formats. " ) ;
2014-02-10 01:10:30 +00:00
2017-08-17 21:35:55 +00:00
int w = next_power_of_2 ( width ) ;
int h = next_power_of_2 ( height ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
if ( w = = width & & h = = height ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
if ( ! p_square | | w = = h )
2014-02-10 01:10:30 +00:00
return ; //nothing to do
}
2017-03-05 15:44:50 +00:00
resize ( w , h ) ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
void Image : : resize ( int p_width , int p_height , Interpolation p_interpolation ) {
2014-02-10 01:10:30 +00:00
2019-08-15 02:57:49 +00:00
ERR_FAIL_COND_MSG ( data . size ( ) = = 0 , " Cannot resize image before creating it, use create() or create_from_data() first. " ) ;
2018-01-06 12:10:37 +00:00
2019-08-26 15:21:19 +00:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot resize in compressed or custom image formats. " ) ;
2014-02-10 01:10:30 +00:00
2018-06-01 19:53:35 +00:00
bool mipmap_aware = p_interpolation = = INTERPOLATE_TRILINEAR /* || p_interpolation == INTERPOLATE_TRICUBIC */ ;
2019-10-28 07:07:29 +00: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 08:28:50 +00: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 ) + " . " ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
if ( p_width = = width & & p_height = = height )
2014-02-10 01:10:30 +00:00
return ;
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
Image dst ( p_width , p_height , 0 , format ) ;
2016-03-08 23:00:52 +00:00
2018-06-01 19:53:35 +00:00
// Setup mipmap-aware scaling
Image dst2 ;
2018-10-04 11:04:58 +00:00
int mip1 = 0 ;
int mip2 = 0 ;
float mip1_weight = 0 ;
2018-06-01 19:53:35 +00: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 ) {
dst2 . create ( p_width , p_height , 0 , format ) ;
}
2018-10-03 15:27:22 +00:00
2018-06-01 19:53:35 +00:00
bool had_mipmaps = mipmaps ;
if ( interpolate_mipmaps & & ! had_mipmaps ) {
generate_mipmaps ( ) ;
}
// --
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Read r = data . read ( ) ;
2017-03-05 15:44:50 +00:00
const unsigned char * r_ptr = r . ptr ( ) ;
2014-02-10 01:10:30 +00:00
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write w = dst . data . write ( ) ;
2017-03-05 15:44:50 +00:00
unsigned char * w_ptr = w . ptr ( ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
switch ( p_interpolation ) {
2014-02-10 01:10:30 +00:00
case INTERPOLATE_NEAREST : {
2018-08-22 19:40:43 +00:00
if ( format > = FORMAT_L8 & & format < = FORMAT_RGBA8 ) {
switch ( get_format_pixel_size ( format ) ) {
2018-08-22 20:56:47 +00: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 19:40:43 +00:00
}
} else if ( format > = FORMAT_RF & & format < = FORMAT_RGBAF ) {
switch ( get_format_pixel_size ( format ) ) {
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 ;
}
} else if ( format > = FORMAT_RH & & format < = FORMAT_RGBAH ) {
switch ( get_format_pixel_size ( format ) ) {
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 ;
}
2014-02-10 01:10:30 +00:00
}
2018-08-22 19:40:43 +00:00
2014-02-10 01:10:30 +00:00
} break ;
2018-06-01 19:53:35 +00: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
w = dst2 . data . write ( ) ;
w_ptr = w . ptr ( ) ;
}
}
2014-02-10 01:10:30 +00:00
2018-08-22 19:40:43 +00:00
if ( format > = FORMAT_L8 & & format < = FORMAT_RGBA8 ) {
switch ( get_format_pixel_size ( format ) ) {
2018-08-22 20:56:47 +00: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 19:40:43 +00:00
}
} else if ( format > = FORMAT_RF & & format < = FORMAT_RGBAF ) {
switch ( get_format_pixel_size ( format ) ) {
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 ;
}
} else if ( format > = FORMAT_RH & & format < = FORMAT_RGBAH ) {
switch ( get_format_pixel_size ( format ) ) {
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-06-01 19:53:35 +00:00
}
}
if ( interpolate_mipmaps ) {
// Switch to read again from the first scaled mipmap to overlay it over the second
r = dst . data . read ( ) ;
_overlay ( r . ptr ( ) , w . ptr ( ) , mip1_weight , p_width , p_height , get_format_pixel_size ( format ) ) ;
2014-02-10 01:10:30 +00:00
}
} break ;
2015-10-01 19:25:36 +00:00
case INTERPOLATE_CUBIC : {
2018-08-22 19:40:43 +00:00
if ( format > = FORMAT_L8 & & format < = FORMAT_RGBA8 ) {
switch ( get_format_pixel_size ( format ) ) {
2018-08-22 20:56:47 +00: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 19:40:43 +00:00
}
} else if ( format > = FORMAT_RF & & format < = FORMAT_RGBAF ) {
switch ( get_format_pixel_size ( format ) ) {
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 ;
}
} else if ( format > = FORMAT_RH & & format < = FORMAT_RGBAH ) {
switch ( get_format_pixel_size ( format ) ) {
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 ;
}
2015-10-01 19:25:36 +00:00
}
} break ;
2019-05-05 12:03:52 +00:00
case INTERPOLATE_LANCZOS : {
if ( format > = FORMAT_L8 & & format < = FORMAT_RGBA8 ) {
switch ( get_format_pixel_size ( format ) ) {
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 ;
}
} else if ( format > = FORMAT_RF & & format < = FORMAT_RGBAF ) {
switch ( get_format_pixel_size ( format ) ) {
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 ;
}
} else if ( format > = FORMAT_RH & & format < = FORMAT_RGBAH ) {
switch ( get_format_pixel_size ( format ) ) {
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 ;
}
}
} break ;
2014-02-10 01:10:30 +00:00
}
2019-07-05 17:08:43 +00:00
r . release ( ) ;
w . release ( ) ;
2014-02-10 01:10:30 +00:00
2018-06-01 19:53:35 +00:00
if ( interpolate_mipmaps ) {
dst . _copy_internals_from ( dst2 ) ;
}
if ( had_mipmaps )
2014-02-10 01:10:30 +00:00
dst . generate_mipmaps ( ) ;
2017-05-17 10:36:47 +00:00
_copy_internals_from ( dst ) ;
2014-02-10 01:10:30 +00:00
}
2017-11-18 03:42:14 +00:00
void Image : : crop_from_point ( int p_x , int p_y , int p_width , int p_height ) {
2018-10-03 15:27:22 +00:00
2019-08-26 15:21:19 +00:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot crop in compressed or custom image formats. " ) ;
2019-08-15 02:57:49 +00:00
2019-09-25 08:28:50 +00: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-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
/* to save memory, cropping should be done in-place, however, since this function
2018-01-18 20:37:17 +00:00
will most likely either not be used much , or in critical areas , for now it won ' t , because
2014-02-10 01:10:30 +00:00
it ' s a waste of time . */
2017-11-18 03:42:14 +00:00
if ( p_width = = width & & p_height = = height & & p_x = = 0 & & p_y = = 0 )
2014-02-10 01:10:30 +00:00
return ;
2016-03-08 23:00:52 +00:00
2016-10-03 19:33:42 +00:00
uint8_t pdata [ 16 ] ; //largest is 16
uint32_t pixel_size = get_format_pixel_size ( format ) ;
2017-03-05 15:44:50 +00:00
Image dst ( p_width , p_height , 0 , format ) ;
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
{
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Read r = data . read ( ) ;
PoolVector < uint8_t > : : Write w = dst . data . write ( ) ;
2016-03-08 23:00:52 +00:00
2017-11-18 03:42:14 +00:00
int m_h = p_y + p_height ;
int m_w = p_x + p_width ;
for ( int y = p_y ; y < m_h ; y + + ) {
2016-03-08 23:00:52 +00:00
2017-11-18 03:42:14 +00:00
for ( int x = p_x ; x < m_w ; x + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
if ( ( x > = width | | y > = height ) ) {
for ( uint32_t i = 0 ; i < pixel_size ; i + + )
pdata [ i ] = 0 ;
2016-10-03 19:33:42 +00:00
} else {
2017-03-05 15:44:50 +00:00
_get_pixelb ( x , y , pixel_size , r . ptr ( ) , pdata ) ;
2016-10-03 19:33:42 +00:00
}
2017-11-18 03:42:14 +00:00
dst . _put_pixelb ( x - p_x , y - p_y , pixel_size , w . ptr ( ) , pdata ) ;
2016-10-03 19:33:42 +00:00
}
2014-02-10 01:10:30 +00:00
}
}
2016-03-08 23:00:52 +00:00
2018-10-03 15:27:22 +00:00
if ( has_mipmaps ( ) )
2014-02-10 01:10:30 +00:00
dst . generate_mipmaps ( ) ;
2017-05-17 10:36:47 +00:00
_copy_internals_from ( dst ) ;
2014-02-10 01:10:30 +00:00
}
2017-11-18 03:42:14 +00:00
void Image : : crop ( int p_width , int p_height ) {
crop_from_point ( 0 , 0 , p_width , p_height ) ;
}
2014-02-10 01:10:30 +00:00
void Image : : flip_y ( ) {
2016-03-08 23:00:52 +00:00
2019-08-26 15:21:19 +00:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot flip_y in compressed or custom image formats. " ) ;
2014-02-10 01:10:30 +00:00
2018-10-03 15:27:22 +00:00
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
2017-01-14 17:03:38 +00:00
clear_mipmaps ( ) ;
2018-10-03 15:27:22 +00:00
}
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
{
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write w = data . write ( ) ;
2016-10-03 19:33:42 +00:00
uint8_t up [ 16 ] ;
uint8_t down [ 16 ] ;
uint32_t pixel_size = get_format_pixel_size ( format ) ;
2014-02-10 01:10:30 +00:00
2017-06-09 03:23:50 +00:00
for ( int y = 0 ; y < height / 2 ; y + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( int x = 0 ; x < width ; x + + ) {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
_get_pixelb ( x , y , pixel_size , w . ptr ( ) , up ) ;
_get_pixelb ( x , height - y - 1 , pixel_size , w . ptr ( ) , down ) ;
2016-10-03 19:33:42 +00:00
2017-03-05 15:44:50 +00:00
_put_pixelb ( x , height - y - 1 , pixel_size , w . ptr ( ) , up ) ;
_put_pixelb ( x , y , pixel_size , w . ptr ( ) , down ) ;
2016-10-03 19:33:42 +00:00
}
2014-02-10 01:10:30 +00:00
}
}
2016-10-03 19:33:42 +00:00
2018-10-03 15:27:22 +00:00
if ( used_mipmaps ) {
2017-01-14 17:03:38 +00:00
generate_mipmaps ( ) ;
2018-10-03 15:27:22 +00:00
}
2014-02-10 01:10:30 +00:00
}
void Image : : flip_x ( ) {
2019-08-26 15:21:19 +00:00
ERR_FAIL_COND_MSG ( ! _can_modify ( format ) , " Cannot flip_x in compressed or custom image formats. " ) ;
2014-02-10 01:10:30 +00:00
2018-10-03 15:27:22 +00:00
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
2017-01-14 17:03:38 +00:00
clear_mipmaps ( ) ;
2018-10-03 15:27:22 +00:00
}
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
{
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write w = data . write ( ) ;
2016-10-03 19:33:42 +00:00
uint8_t up [ 16 ] ;
uint8_t down [ 16 ] ;
uint32_t pixel_size = get_format_pixel_size ( format ) ;
2017-03-05 15:44:50 +00:00
for ( int y = 0 ; y < height ; y + + ) {
2016-03-08 23:00:52 +00:00
2017-06-09 03:23:50 +00:00
for ( int x = 0 ; x < width / 2 ; x + + ) {
2016-10-03 19:33:42 +00:00
2017-03-05 15:44:50 +00:00
_get_pixelb ( x , y , pixel_size , w . ptr ( ) , up ) ;
_get_pixelb ( width - x - 1 , y , pixel_size , w . ptr ( ) , down ) ;
2016-10-03 19:33:42 +00:00
2017-03-05 15:44:50 +00:00
_put_pixelb ( width - x - 1 , y , pixel_size , w . ptr ( ) , up ) ;
_put_pixelb ( x , y , pixel_size , w . ptr ( ) , down ) ;
2016-10-03 19:33:42 +00:00
}
2014-02-10 01:10:30 +00:00
}
}
2018-10-03 15:27:22 +00:00
if ( used_mipmaps ) {
2017-01-14 17:03:38 +00:00
generate_mipmaps ( ) ;
2018-10-03 15:27:22 +00:00
}
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
int Image : : _get_dst_image_size ( int p_width , int p_height , Format p_format , int & r_mipmaps , int p_mipmaps ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
int size = 0 ;
int w = p_width ;
int h = p_height ;
int mm = 0 ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
int pixsize = get_format_pixel_size ( p_format ) ;
int pixshift = get_format_pixel_rshift ( p_format ) ;
2017-05-27 00:49:49 +00:00
int block = get_format_block_size ( p_format ) ;
2018-08-06 17:56:06 +00:00
//technically, you can still compress up to 1 px no matter the format, so commenting this
//int minw, minh;
//get_format_min_pixel_size(p_format, minw, minh);
int minw = 1 , minh = 1 ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
while ( true ) {
2014-02-10 01:10:30 +00:00
2017-05-27 00:49:49 +00: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 ;
2017-03-05 15:44:50 +00:00
s * = pixsize ;
s > > = pixshift ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
size + = s ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
if ( p_mipmaps > = 0 & & mm = = p_mipmaps )
2014-02-10 01:10:30 +00:00
break ;
2017-03-05 15:44:50 +00:00
if ( p_mipmaps > = 0 ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
w = MAX ( minw , w > > 1 ) ;
h = MAX ( minh , h > > 1 ) ;
2014-02-10 01:10:30 +00:00
} else {
2017-03-05 15:44:50 +00:00
if ( w = = minw & & h = = minh )
2014-02-10 01:10:30 +00:00
break ;
2017-03-05 15:44:50 +00:00
w = MAX ( minw , w > > 1 ) ;
h = MAX ( minh , h > > 1 ) ;
2014-02-10 01:10:30 +00:00
}
mm + + ;
} ;
2017-03-05 15:44:50 +00:00
r_mipmaps = mm ;
2014-02-10 01:10:30 +00:00
return size ;
}
bool Image : : _can_modify ( Format p_format ) const {
2017-05-27 00:49:49 +00:00
return p_format < = FORMAT_RGBE9995 ;
2014-02-10 01:10:30 +00:00
}
2018-08-22 02:56:04 +00: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 01:10:30 +00:00
//fast power of 2 mipmap generation
2018-08-25 20:39:43 +00:00
uint32_t dst_w = MAX ( p_width > > 1 , 1 ) ;
uint32_t dst_h = MAX ( p_height > > 1 , 1 ) ;
int right_step = ( p_width = = 1 ) ? 0 : CC ;
int down_step = ( p_height = = 1 ) ? 0 : ( p_width * CC ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( uint32_t i = 0 ; i < dst_h ; i + + ) {
2014-02-10 01:10:30 +00:00
2018-08-25 20:39:43 +00:00
const Component * rup_ptr = & p_src [ i * 2 * down_step ] ;
const Component * rdown_ptr = rup_ptr + down_step ;
2018-08-22 02:56:04 +00:00
Component * dst_ptr = & p_dst [ i * dst_w * CC ] ;
2017-03-05 15:44:50 +00:00
uint32_t count = dst_w ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
while ( count - - ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( int j = 0 ; j < CC ; j + + ) {
2018-08-25 20:39:43 +00: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 01:10:30 +00:00
}
2018-04-30 00:51:37 +00:00
if ( renormalize ) {
2018-08-22 02:56:04 +00:00
renormalize_func ( dst_ptr ) ;
2018-04-30 00:51:37 +00:00
}
2017-03-05 15:44:50 +00:00
dst_ptr + = CC ;
2018-08-25 20:39:43 +00:00
rup_ptr + = right_step * 2 ;
rdown_ptr + = right_step * 2 ;
2014-02-10 01:10:30 +00:00
}
}
}
2016-05-29 14:37:26 +00:00
void Image : : expand_x2_hq2x ( ) {
2016-10-03 19:33:42 +00:00
ERR_FAIL_COND ( ! _can_modify ( format ) ) ;
2016-05-29 14:37:26 +00:00
2018-10-03 15:27:22 +00:00
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
2016-05-29 14:37:26 +00:00
clear_mipmaps ( ) ;
}
2018-10-03 15:27:22 +00:00
Format current = format ;
2017-03-05 15:44:50 +00:00
if ( current ! = FORMAT_RGBA8 )
2016-10-03 19:33:42 +00:00
convert ( FORMAT_RGBA8 ) ;
2016-05-29 14:37:26 +00:00
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > dest ;
2017-03-05 15:44:50 +00:00
dest . resize ( width * 2 * height * 2 * 4 ) ;
2016-05-29 14:37:26 +00:00
{
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Read r = data . read ( ) ;
PoolVector < uint8_t > : : Write w = dest . write ( ) ;
2016-05-29 14:37:26 +00:00
2019-10-28 07:07:29 +00:00
ERR_FAIL_COND ( ! r . ptr ( ) ) ;
2017-03-05 15:44:50 +00:00
hq2x_resize ( ( const uint32_t * ) r . ptr ( ) , width , height , ( uint32_t * ) w . ptr ( ) ) ;
2016-05-29 14:37:26 +00:00
}
2017-03-05 15:44:50 +00:00
width * = 2 ;
height * = 2 ;
data = dest ;
2016-05-29 14:37:26 +00:00
2017-03-05 15:44:50 +00:00
if ( current ! = FORMAT_RGBA8 )
2016-05-29 14:37:26 +00:00
convert ( current ) ;
2018-10-06 15:34:59 +00:00
// FIXME: This is likely meant to use "used_mipmaps" as defined above, but if we do,
// we end up with a regression: GH-22747
if ( mipmaps ) {
2016-05-29 14:37:26 +00:00
generate_mipmaps ( ) ;
}
}
2016-05-04 15:36:51 +00:00
void Image : : shrink_x2 ( ) {
2017-03-05 15:44:50 +00:00
ERR_FAIL_COND ( data . size ( ) = = 0 ) ;
2016-05-04 15:36:51 +00:00
if ( mipmaps ) {
//just use the lower mipmap as base and copy all
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > new_img ;
2016-05-04 15:36:51 +00:00
int ofs = get_mipmap_offset ( 1 ) ;
2017-03-05 15:44:50 +00:00
int new_size = data . size ( ) - ofs ;
2016-05-04 15:36:51 +00:00
new_img . resize ( new_size ) ;
2019-06-22 12:52:51 +00:00
ERR_FAIL_COND ( new_img . size ( ) = = 0 ) ;
2016-05-04 15:36:51 +00:00
{
2017-03-05 15:44:50 +00:00
PoolVector < uint8_t > : : Write w = new_img . write ( ) ;
PoolVector < uint8_t > : : Read r = data . read ( ) ;
2016-05-04 15:36:51 +00:00
2017-03-05 15:44:50 +00:00
copymem ( w . ptr ( ) , & r [ ofs ] , new_size ) ;
2016-05-04 15:36:51 +00:00
}
2017-09-30 20:21:10 +00:00
width = MAX ( width / 2 , 1 ) ;
height = MAX ( height / 2 , 1 ) ;
2017-03-05 15:44:50 +00:00
data = new_img ;
2016-05-04 15:36:51 +00:00
} else {
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > new_img ;
2016-05-04 15:36:51 +00:00
2017-03-05 15:44:50 +00:00
ERR_FAIL_COND ( ! _can_modify ( format ) ) ;
2016-05-04 15:36:51 +00:00
int ps = get_format_pixel_size ( format ) ;
2017-03-05 15:44:50 +00:00
new_img . resize ( ( width / 2 ) * ( height / 2 ) * ps ) ;
2019-06-22 12:52:51 +00:00
ERR_FAIL_COND ( new_img . size ( ) = = 0 ) ;
2016-05-04 15:36:51 +00:00
{
2017-03-05 15:44:50 +00:00
PoolVector < uint8_t > : : Write w = new_img . write ( ) ;
PoolVector < uint8_t > : : Read r = data . read ( ) ;
2016-05-04 15:36:51 +00:00
2017-03-05 15:44:50 +00:00
switch ( format ) {
2016-05-04 15:36:51 +00:00
2016-10-03 19:33:42 +00:00
case FORMAT_L8 :
2018-08-22 02:56:04 +00:00
case FORMAT_R8 : _generate_po2_mipmap < uint8_t , 1 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r . ptr ( ) , w . ptr ( ) , width , height ) ; break ;
case FORMAT_LA8 : _generate_po2_mipmap < uint8_t , 2 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r . ptr ( ) , w . ptr ( ) , width , height ) ; break ;
case FORMAT_RG8 : _generate_po2_mipmap < uint8_t , 2 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r . ptr ( ) , w . ptr ( ) , width , height ) ; break ;
case FORMAT_RGB8 : _generate_po2_mipmap < uint8_t , 3 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r . ptr ( ) , w . ptr ( ) , width , height ) ; break ;
case FORMAT_RGBA8 : _generate_po2_mipmap < uint8_t , 4 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( r . ptr ( ) , w . ptr ( ) , width , height ) ; break ;
case FORMAT_RF : _generate_po2_mipmap < float , 1 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( r . ptr ( ) ) , reinterpret_cast < float * > ( w . ptr ( ) ) , width , height ) ; break ;
case FORMAT_RGF : _generate_po2_mipmap < float , 2 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( r . ptr ( ) ) , reinterpret_cast < float * > ( w . ptr ( ) ) , width , height ) ; break ;
case FORMAT_RGBF : _generate_po2_mipmap < float , 3 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( r . ptr ( ) ) , reinterpret_cast < float * > ( w . ptr ( ) ) , width , height ) ; break ;
case FORMAT_RGBAF : _generate_po2_mipmap < float , 4 , false , Image : : average_4_float , Image : : renormalize_float > ( reinterpret_cast < const float * > ( r . ptr ( ) ) , reinterpret_cast < float * > ( w . ptr ( ) ) , 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 . ptr ( ) ) , reinterpret_cast < uint16_t * > ( w . ptr ( ) ) , 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 . ptr ( ) ) , reinterpret_cast < uint16_t * > ( w . ptr ( ) ) , 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 . ptr ( ) ) , reinterpret_cast < uint16_t * > ( w . ptr ( ) ) , 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 . ptr ( ) ) , reinterpret_cast < uint16_t * > ( w . ptr ( ) ) , 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 . ptr ( ) ) , reinterpret_cast < uint32_t * > ( w . ptr ( ) ) , width , height ) ; break ;
2019-04-09 15:08:36 +00:00
default : {
}
2016-05-04 15:36:51 +00:00
}
}
2017-03-05 15:44:50 +00:00
width / = 2 ;
height / = 2 ;
data = new_img ;
2016-05-04 15:36:51 +00:00
}
}
2018-07-03 13:55:50 +00:00
void Image : : normalize ( ) {
bool used_mipmaps = has_mipmaps ( ) ;
if ( used_mipmaps ) {
clear_mipmaps ( ) ;
}
lock ( ) ;
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 ) ;
}
}
unlock ( ) ;
if ( used_mipmaps ) {
generate_mipmaps ( true ) ;
}
}
2018-04-30 00:51:37 +00:00
Error Image : : generate_mipmaps ( bool p_renormalize ) {
2014-02-10 01:10:30 +00:00
2019-08-26 15:21:19 +00:00
ERR_FAIL_COND_V_MSG ( ! _can_modify ( format ) , ERR_UNAVAILABLE , " Cannot generate mipmaps in compressed or custom image formats. " ) ;
2014-02-10 01:10:30 +00:00
2019-08-15 02:57:49 +00: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 03:38:39 +00:00
int mmcount ;
2016-10-03 19:33:42 +00:00
2017-03-05 15:44:50 +00:00
int size = _get_dst_image_size ( width , height , format , mmcount ) ;
2014-02-10 01:10:30 +00:00
data . resize ( size ) ;
2017-03-05 15:44:50 +00:00
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
2014-02-10 01:10:30 +00:00
2018-03-03 21:19:31 +00:00
int prev_ofs = 0 ;
int prev_h = height ;
int prev_w = width ;
2014-02-10 01:10:30 +00:00
2018-08-25 20:39:43 +00:00
for ( int i = 1 ; i < = mmcount ; i + + ) {
2014-02-10 01:10:30 +00:00
2018-03-03 21:19:31 +00:00
int ofs , w , h ;
_get_mipmap_offset_and_size ( i , ofs , w , h ) ;
2014-02-10 01:10:30 +00:00
2018-03-03 21:19:31 +00:00
switch ( format ) {
2014-02-10 01:10:30 +00:00
2018-03-03 21:19:31 +00:00
case FORMAT_L8 :
2018-08-22 02:56:04 +00: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 21:19:31 +00:00
case FORMAT_LA8 :
2018-08-22 02:56:04 +00: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 00:51:37 +00:00
case FORMAT_RGB8 :
if ( p_renormalize )
2018-08-22 02:56:04 +00:00
_generate_po2_mipmap < uint8_t , 3 , true , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
2018-04-30 00:51:37 +00:00
else
2018-08-22 02:56:04 +00:00
_generate_po2_mipmap < uint8_t , 3 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
2018-04-30 00:51:37 +00:00
break ;
case FORMAT_RGBA8 :
if ( p_renormalize )
2018-08-22 02:56:04 +00:00
_generate_po2_mipmap < uint8_t , 4 , true , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
else
_generate_po2_mipmap < uint8_t , 4 , false , Image : : average_4_uint8 , Image : : renormalize_uint8 > ( & wp [ prev_ofs ] , & wp [ ofs ] , prev_w , prev_h ) ;
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 :
if ( p_renormalize )
_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 ) ;
else
_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 ) ;
break ;
case FORMAT_RGBAF :
if ( p_renormalize )
_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 ) ;
else
_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 ) ;
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 :
if ( p_renormalize )
_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 ) ;
else
_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 ) ;
break ;
case FORMAT_RGBAH :
if ( p_renormalize )
_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 ) ;
else
_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 ) ;
break ;
case FORMAT_RGBE9995 :
if ( p_renormalize )
_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 ) ;
2018-04-30 00:51:37 +00:00
else
2018-08-22 02:56:04 +00: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 ) ;
2018-04-30 00:51:37 +00:00
break ;
2019-04-09 15:08:36 +00:00
default : {
}
2014-02-10 01:10:30 +00:00
}
2018-03-03 21:19:31 +00:00
prev_ofs = ofs ;
prev_w = w ;
prev_h = h ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
mipmaps = true ;
2014-02-10 01:10:30 +00:00
return OK ;
}
void Image : : clear_mipmaps ( ) {
2016-10-03 19:33:42 +00:00
if ( ! mipmaps )
2014-02-10 01:10:30 +00:00
return ;
if ( empty ( ) )
return ;
2017-03-05 15:44:50 +00:00
int ofs , w , h ;
_get_mipmap_offset_and_size ( 1 , ofs , w , h ) ;
2016-10-03 19:33:42 +00:00
data . resize ( ofs ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
mipmaps = false ;
2014-02-10 01:10:30 +00:00
}
bool Image : : empty ( ) const {
2017-03-05 15:44:50 +00:00
return ( data . size ( ) = = 0 ) ;
2014-02-10 01:10:30 +00:00
}
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > Image : : get_data ( ) const {
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
return data ;
}
2017-03-05 15:44:50 +00:00
void Image : : create ( int p_width , int p_height , bool p_use_mipmaps , Format p_format ) {
2014-02-10 01:10:30 +00:00
2018-05-05 08:46:51 +00:00
ERR_FAIL_INDEX ( p_width - 1 , MAX_WIDTH ) ;
ERR_FAIL_INDEX ( p_height - 1 , MAX_HEIGHT ) ;
2017-03-05 15:44:50 +00:00
int mm = 0 ;
int size = _get_dst_image_size ( p_width , p_height , p_format , mm , p_use_mipmaps ? - 1 : 0 ) ;
data . resize ( size ) ;
2014-02-10 01:10:30 +00:00
{
2017-03-05 15:44:50 +00:00
PoolVector < uint8_t > : : Write w = data . write ( ) ;
zeromem ( w . ptr ( ) , size ) ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
width = p_width ;
height = p_height ;
mipmaps = p_use_mipmaps ;
format = p_format ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
void Image : : create ( int p_width , int p_height , bool p_use_mipmaps , Format p_format , const PoolVector < uint8_t > & p_data ) {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
ERR_FAIL_INDEX ( p_width - 1 , MAX_WIDTH ) ;
ERR_FAIL_INDEX ( p_height - 1 , MAX_HEIGHT ) ;
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
int mm ;
2017-03-05 15:44:50 +00:00
int size = _get_dst_image_size ( p_width , p_height , p_format , mm , p_use_mipmaps ? - 1 : 0 ) ;
2014-02-10 01:10:30 +00:00
2019-08-15 02:57:49 +00:00
ERR_FAIL_COND_MSG ( p_data . size ( ) ! = size , " Expected data size of " + itos ( size ) + " bytes in Image::create(), got instead " + itos ( p_data . size ( ) ) + " bytes. " ) ;
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
height = p_height ;
width = p_width ;
format = p_format ;
data = p_data ;
mipmaps = p_use_mipmaps ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
void Image : : create ( const char * * p_xpm ) {
2014-02-10 01:10:30 +00:00
2018-10-04 16:54:20 +00:00
int size_width = 0 ;
int size_height = 0 ;
2017-03-05 15:44:50 +00:00
int pixelchars = 0 ;
mipmaps = false ;
bool has_alpha = false ;
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
enum Status {
READING_HEADER ,
READING_COLORS ,
READING_PIXELS ,
DONE
} ;
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
Status status = READING_HEADER ;
2017-03-05 15:44:50 +00:00
int line = 0 ;
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
HashMap < String , Color > colormap ;
2018-10-04 16:54:20 +00:00
int colormap_size = 0 ;
uint32_t pixel_size = 0 ;
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write w ;
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
while ( status ! = DONE ) {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
const char * line_ptr = p_xpm [ line ] ;
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
switch ( status ) {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
case READING_HEADER : {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
String line_str = line_ptr ;
line_str . replace ( " \t " , " " ) ;
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00: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 ( ) ;
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 : {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
String colorstring ;
for ( int i = 0 ; i < pixelchars ; i + + ) {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
colorstring + = * line_ptr ;
line_ptr + + ;
}
2014-02-10 01:10:30 +00:00
//skip spaces
2017-03-05 15:44:50 +00:00
while ( * line_ptr = = ' ' | | * line_ptr = = ' \t ' | | * line_ptr = = 0 ) {
if ( * line_ptr = = 0 )
2014-02-10 01:10:30 +00:00
break ;
line_ptr + + ;
}
2017-03-05 15:44:50 +00:00
if ( * line_ptr = = ' c ' ) {
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
line_ptr + + ;
2017-03-05 15:44:50 +00:00
while ( * line_ptr = = ' ' | | * line_ptr = = ' \t ' | | * line_ptr = = 0 ) {
if ( * line_ptr = = 0 )
2014-02-10 01:10:30 +00:00
break ;
2017-03-05 15:44:50 +00:00
line_ptr + + ;
2014-02-10 01:10:30 +00:00
}
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
if ( * line_ptr = = ' # ' ) {
line_ptr + + ;
2017-09-01 20:33:39 +00:00
uint8_t col_r = 0 ;
uint8_t col_g = 0 ;
uint8_t col_b = 0 ;
2017-03-05 15:44:50 +00:00
//uint8_t col_a=255;
for ( int i = 0 ; i < 6 ; i + + ) {
char v = line_ptr [ i ] ;
if ( v > = ' 0 ' & & v < = ' 9 ' )
v - = ' 0 ' ;
else if ( v > = ' A ' & & v < = ' F ' )
v = ( v - ' A ' ) + 10 ;
else if ( v > = ' a ' & & v < = ' f ' )
v = ( v - ' a ' ) + 10 ;
else
break ;
switch ( i ) {
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 ;
} ;
}
// 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 ) ;
}
2014-02-10 01:10:30 +00:00
}
}
2017-03-05 15:44:50 +00:00
if ( line = = colormap_size ) {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
status = READING_PIXELS ;
create ( size_width , size_height , 0 , has_alpha ? FORMAT_RGBA8 : FORMAT_RGB8 ) ;
w = data . write ( ) ;
pixel_size = has_alpha ? 4 : 3 ;
}
} break ;
case READING_PIXELS : {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
int y = line - colormap_size - 1 ;
for ( int x = 0 ; x < size_width ; x + + ) {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
char pixelstr [ 6 ] = { 0 , 0 , 0 , 0 , 0 , 0 } ;
for ( int i = 0 ; i < pixelchars ; i + + )
pixelstr [ i ] = line_ptr [ x * pixelchars + i ] ;
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
Color * colorptr = colormap . getptr ( pixelstr ) ;
ERR_FAIL_COND ( ! colorptr ) ;
uint8_t pixel [ 4 ] ;
for ( uint32_t i = 0 ; i < pixel_size ; i + + ) {
pixel [ i ] = CLAMP ( ( * colorptr ) [ i ] * 255 , 0 , 255 ) ;
}
_put_pixelb ( x , y , pixel_size , w . ptr ( ) , pixel ) ;
2016-10-03 19:33:42 +00:00
}
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
if ( y = = ( size_height - 1 ) )
status = DONE ;
} break ;
2019-04-09 15:08:36 +00:00
default : {
}
2014-02-10 01:10:30 +00:00
}
2016-03-08 23:00:52 +00:00
2014-02-10 01:10:30 +00:00
line + + ;
}
}
2017-07-08 15:12:18 +00: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 ; \
} \
2017-03-05 15:44:50 +00:00
}
2015-06-01 22:42:34 +00:00
2017-03-05 15:44:50 +00:00
# define DETECT_NON_ALPHA(m_value) \
{ \
uint8_t value = m_value ; \
if ( value > 0 ) { \
\
detected = true ; \
break ; \
} \
}
2015-06-01 22:42:34 +00:00
bool Image : : is_invisible ( ) const {
2017-03-05 15:44:50 +00:00
if ( format = = FORMAT_L8 | |
format = = FORMAT_RGB8 | | format = = FORMAT_RG8 )
2015-06-01 22:42:34 +00:00
return false ;
int len = data . size ( ) ;
2017-03-05 15:44:50 +00:00
if ( len = = 0 )
2015-06-01 22:42:34 +00:00
return true ;
2017-03-05 15:44:50 +00:00
int w , h ;
_get_mipmap_offset_and_size ( 1 , len , w , h ) ;
2015-06-01 22:42:34 +00:00
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Read r = data . read ( ) ;
2017-03-05 15:44:50 +00:00
const unsigned char * data_ptr = r . ptr ( ) ;
2015-06-01 22:42:34 +00:00
2017-03-05 15:44:50 +00:00
bool detected = false ;
2015-06-01 22:42:34 +00:00
2017-03-05 15:44:50 +00:00
switch ( format ) {
2015-06-01 22:42:34 +00:00
2016-10-03 19:33:42 +00:00
case FORMAT_LA8 : {
2015-06-01 22:42:34 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < ( len > > 1 ) ; i + + ) {
DETECT_NON_ALPHA ( data_ptr [ ( i < < 1 ) + 1 ] ) ;
2015-06-01 22:42:34 +00:00
}
} break ;
2016-10-03 19:33:42 +00:00
case FORMAT_RGBA8 : {
2015-06-01 22:42:34 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < ( len > > 2 ) ; i + + ) {
DETECT_NON_ALPHA ( data_ptr [ ( i < < 2 ) + 3 ] )
2015-06-01 22:42:34 +00:00
}
} break ;
2016-10-03 19:33:42 +00:00
case FORMAT_PVRTC2A :
case FORMAT_PVRTC4A :
case FORMAT_DXT3 :
case FORMAT_DXT5 : {
2017-03-05 15:44:50 +00:00
detected = true ;
2015-06-01 22:42:34 +00:00
} break ;
2019-04-09 15:08:36 +00:00
default : {
}
2015-06-01 22:42:34 +00:00
}
return ! detected ;
}
2014-02-10 01:10:30 +00:00
Image : : AlphaMode Image : : detect_alpha ( ) const {
int len = data . size ( ) ;
2017-03-05 15:44:50 +00:00
if ( len = = 0 )
2014-02-10 01:10:30 +00:00
return ALPHA_NONE ;
2017-03-05 15:44:50 +00:00
int w , h ;
_get_mipmap_offset_and_size ( 1 , len , w , h ) ;
2014-02-10 01:10:30 +00:00
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Read r = data . read ( ) ;
2017-03-05 15:44:50 +00:00
const unsigned char * data_ptr = r . ptr ( ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
bool bit = false ;
bool detected = false ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
switch ( format ) {
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
case FORMAT_LA8 : {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < ( len > > 1 ) ; i + + ) {
DETECT_ALPHA ( data_ptr [ ( i < < 1 ) + 1 ] ) ;
2014-02-10 01:10:30 +00:00
}
} break ;
2016-10-03 19:33:42 +00:00
case FORMAT_RGBA8 : {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < ( len > > 2 ) ; i + + ) {
DETECT_ALPHA ( data_ptr [ ( i < < 2 ) + 3 ] )
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
} break ;
2016-10-03 19:33:42 +00:00
case FORMAT_PVRTC2A :
case FORMAT_PVRTC4A :
case FORMAT_DXT3 :
case FORMAT_DXT5 : {
2017-03-05 15:44:50 +00:00
detected = true ;
2014-02-10 01:10:30 +00:00
} break ;
2019-04-09 15:08:36 +00:00
default : {
}
2014-02-10 01:10:30 +00:00
}
if ( detected )
return ALPHA_BLEND ;
else if ( bit )
return ALPHA_BIT ;
else
return ALPHA_NONE ;
}
2017-03-05 15:44:50 +00:00
Error Image : : load ( const String & p_path ) {
2018-08-14 19:52:40 +00:00
# ifdef DEBUG_ENABLED
if ( p_path . begins_with ( " res:// " ) & & ResourceLoader : : exists ( p_path ) ) {
WARN_PRINTS ( " 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
2014-02-10 01:10:30 +00:00
return ImageLoader : : load_image ( p_path , this ) ;
}
2017-04-30 01:14:14 +00:00
Error Image : : save_png ( const String & p_path ) const {
2014-11-13 03:53:12 +00:00
if ( save_png_func = = NULL )
return ERR_UNAVAILABLE ;
2017-06-09 03:23:50 +00:00
return save_png_func ( p_path , Ref < Image > ( ( Image * ) this ) ) ;
2014-02-10 01:10:30 +00:00
}
2019-08-03 00:07:13 +00:00
Error Image : : save_exr ( const String & p_path , bool p_grayscale ) const {
if ( save_exr_func = = NULL )
return ERR_UNAVAILABLE ;
return save_exr_func ( p_path , Ref < Image > ( ( Image * ) this ) , p_grayscale ) ;
}
2018-08-06 17:56:06 +00:00
int Image : : get_image_data_size ( int p_width , int p_height , Format p_format , bool p_mipmaps ) {
2014-02-10 01:10:30 +00:00
int mm ;
2018-08-06 17:56:06 +00:00
return _get_dst_image_size ( p_width , p_height , p_format , mm , p_mipmaps ? - 1 : 0 ) ;
2014-02-10 01:10:30 +00:00
}
int Image : : get_image_required_mipmaps ( int p_width , int p_height , Format p_format ) {
int mm ;
2017-03-05 15:44:50 +00:00
_get_dst_image_size ( p_width , p_height , p_format , mm , - 1 ) ;
2014-02-10 01:10:30 +00:00
return mm ;
}
2018-11-16 22:12:00 +00: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 ) ;
}
2015-05-31 04:59:42 +00:00
bool Image : : is_compressed ( ) const {
2017-05-27 00:49:49 +00:00
return format > FORMAT_RGBE9995 ;
2015-05-31 04:59:42 +00:00
}
2014-06-11 13:41:03 +00:00
Error Image : : decompress ( ) {
2014-02-10 01:10:30 +00:00
2018-08-22 02:56:04 +00:00
if ( format > = FORMAT_DXT1 & & format < = FORMAT_RGTC_RG & & _image_decompress_bc )
2017-04-30 01:14:14 +00:00
_image_decompress_bc ( this ) ;
2018-08-22 02:56:04 +00:00
else if ( format > = FORMAT_BPTC_RGBA & & format < = FORMAT_BPTC_RGBFU & & _image_decompress_bptc )
_image_decompress_bptc ( this ) ;
2017-03-05 15:44:50 +00:00
else if ( format > = FORMAT_PVRTC2 & & format < = FORMAT_PVRTC4A & & _image_decompress_pvrtc )
2014-02-10 01:10:30 +00:00
_image_decompress_pvrtc ( this ) ;
2017-04-26 15:49:08 +00:00
else if ( format = = FORMAT_ETC & & _image_decompress_etc1 )
_image_decompress_etc1 ( this ) ;
2019-02-08 16:26:23 +00:00
else if ( format > = FORMAT_ETC2_R11 & & format < = FORMAT_ETC2_RGB8A1 & & _image_decompress_etc2 )
2016-10-03 19:33:42 +00:00
_image_decompress_etc2 ( this ) ;
2014-06-11 13:41:03 +00:00
else
return ERR_UNAVAILABLE ;
return OK ;
2014-02-10 01:10:30 +00:00
}
2017-06-17 00:47:28 +00:00
Error Image : : compress ( CompressMode p_mode , CompressSource p_source , float p_lossy_quality ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
switch ( p_mode ) {
2014-02-10 01:10:30 +00:00
2016-10-03 19:33:42 +00:00
case COMPRESS_S3TC : {
2014-02-10 01:10:30 +00:00
ERR_FAIL_COND_V ( ! _image_compress_bc_func , ERR_UNAVAILABLE ) ;
2018-08-22 16:05:25 +00:00
_image_compress_bc_func ( this , p_lossy_quality , p_source ) ;
2014-02-10 01:10:30 +00:00
} break ;
case COMPRESS_PVRTC2 : {
ERR_FAIL_COND_V ( ! _image_compress_pvrtc2_func , ERR_UNAVAILABLE ) ;
_image_compress_pvrtc2_func ( this ) ;
} break ;
case COMPRESS_PVRTC4 : {
ERR_FAIL_COND_V ( ! _image_compress_pvrtc4_func , ERR_UNAVAILABLE ) ;
_image_compress_pvrtc4_func ( this ) ;
} break ;
case COMPRESS_ETC : {
2017-04-26 15:49:08 +00:00
ERR_FAIL_COND_V ( ! _image_compress_etc1_func , ERR_UNAVAILABLE ) ;
_image_compress_etc1_func ( this , p_lossy_quality ) ;
2016-10-03 19:33:42 +00:00
} break ;
case COMPRESS_ETC2 : {
2017-04-26 15:49:08 +00:00
ERR_FAIL_COND_V ( ! _image_compress_etc2_func , ERR_UNAVAILABLE ) ;
2017-06-17 00:47:28 +00:00
_image_compress_etc2_func ( this , p_lossy_quality , p_source ) ;
2014-02-10 01:10:30 +00:00
} break ;
2018-08-22 02:56:04 +00:00
case COMPRESS_BPTC : {
ERR_FAIL_COND_V ( ! _image_compress_bptc_func , ERR_UNAVAILABLE ) ;
_image_compress_bptc_func ( this , p_lossy_quality , p_source ) ;
} break ;
2014-02-10 01:10:30 +00:00
}
return OK ;
}
Image : : Image ( const char * * p_xpm ) {
2016-03-08 23:00:52 +00:00
2017-03-05 15:44:50 +00:00
width = 0 ;
height = 0 ;
mipmaps = false ;
format = FORMAT_L8 ;
2014-02-10 01:10:30 +00:00
create ( p_xpm ) ;
}
2017-03-05 15:44:50 +00:00
Image : : Image ( int p_width , int p_height , bool p_use_mipmaps , Format p_format ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
width = 0 ;
height = 0 ;
mipmaps = p_use_mipmaps ;
format = FORMAT_L8 ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
create ( p_width , p_height , p_use_mipmaps , p_format ) ;
2014-02-10 01:10:30 +00:00
}
2017-03-05 15:44:50 +00:00
Image : : Image ( int p_width , int p_height , bool p_mipmaps , Format p_format , const PoolVector < uint8_t > & p_data ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
width = 0 ;
height = 0 ;
mipmaps = p_mipmaps ;
format = FORMAT_L8 ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
create ( p_width , p_height , p_mipmaps , p_format , p_data ) ;
2014-02-10 01:10:30 +00:00
}
Rect2 Image : : get_used_rect ( ) const {
2019-04-19 18:54:33 +00:00
if ( format ! = FORMAT_LA8 & & format ! = FORMAT_RGBA8 & & format ! = FORMAT_RGBAF & & format ! = FORMAT_RGBAH & & format ! = FORMAT_RGBA4444 & & format ! = FORMAT_RGBA5551 )
2017-03-05 15:44:50 +00:00
return Rect2 ( Point2 ( ) , Size2 ( width , height ) ) ;
2014-02-10 01:10:30 +00:00
int len = data . size ( ) ;
2017-03-05 15:44:50 +00:00
if ( len = = 0 )
2014-02-10 01:10:30 +00:00
return Rect2 ( ) ;
2019-04-19 18:54:33 +00:00
const_cast < Image * > ( this ) - > lock ( ) ;
2017-03-05 15:44:50 +00:00
int minx = 0xFFFFFF , miny = 0xFFFFFFF ;
int maxx = - 1 , maxy = - 1 ;
for ( int j = 0 ; j < height ; j + + ) {
for ( int i = 0 ; i < width ; i + + ) {
2016-10-03 19:33:42 +00:00
2019-04-19 18:54:33 +00:00
bool opaque = get_pixel ( i , j ) . a > 0.99 ;
2014-02-10 01:10:30 +00:00
if ( ! opaque )
continue ;
2017-03-05 15:44:50 +00:00
if ( i > maxx )
maxx = i ;
if ( j > maxy )
maxy = j ;
if ( i < minx )
minx = i ;
if ( j < miny )
miny = j ;
2014-02-10 01:10:30 +00:00
}
}
2019-04-19 18:54:33 +00:00
const_cast < Image * > ( this ) - > unlock ( ) ;
2017-03-05 15:44:50 +00:00
if ( maxx = = - 1 )
2014-02-10 01:10:30 +00:00
return Rect2 ( ) ;
else
2017-03-05 15:44:50 +00:00
return Rect2 ( minx , miny , maxx - minx + 1 , maxy - miny + 1 ) ;
2014-02-10 01:10:30 +00:00
}
2017-05-17 10:36:47 +00:00
Ref < Image > Image : : get_rect ( const Rect2 & p_area ) const {
2014-02-10 01:10:30 +00:00
2017-05-17 10:36:47 +00:00
Ref < Image > img = memnew ( Image ( p_area . size . x , p_area . size . y , mipmaps , format ) ) ;
2017-06-09 03:23:50 +00:00
img - > blit_rect ( Ref < Image > ( ( Image * ) this ) , p_area , Point2 ( 0 , 0 ) ) ;
2014-02-10 01:10:30 +00:00
return img ;
}
2017-05-17 10:36:47 +00:00
void Image : : blit_rect ( const Ref < Image > & p_src , const Rect2 & p_src_rect , const Point2 & p_dest ) {
2014-02-10 01:10:30 +00:00
2019-09-25 08:28:50 +00:00
ERR_FAIL_COND_MSG ( p_src . is_null ( ) , " It's not a reference to a valid Image object. " ) ;
2017-03-05 15:44:50 +00:00
int dsize = data . size ( ) ;
2017-05-17 10:36:47 +00:00
int srcdsize = p_src - > data . size ( ) ;
2017-03-05 15:44:50 +00:00
ERR_FAIL_COND ( dsize = = 0 ) ;
ERR_FAIL_COND ( srcdsize = = 0 ) ;
2017-05-17 10:36:47 +00:00
ERR_FAIL_COND ( format ! = p_src - > format ) ;
2014-02-10 01:10:30 +00:00
2017-06-14 13:56:58 +00:00
Rect2i clipped_src_rect = Rect2i ( 0 , 0 , p_src - > width , p_src - > height ) . clip ( p_src_rect ) ;
2018-04-15 20:37:23 +00:00
if ( p_dest . x < 0 )
clipped_src_rect . position . x = ABS ( p_dest . x ) ;
if ( p_dest . y < 0 )
clipped_src_rect . position . y = ABS ( p_dest . y ) ;
2017-06-14 13:56:58 +00:00
if ( clipped_src_rect . size . x < = 0 | | clipped_src_rect . size . y < = 0 )
2016-10-03 19:33:42 +00:00
return ;
2017-06-14 13:56:58 +00:00
2018-11-27 19:37:02 +00:00
Point2 src_underscan = Point2 ( MIN ( 0 , p_src_rect . position . x ) , MIN ( 0 , p_src_rect . position . y ) ) ;
Rect2i dest_rect = Rect2i ( 0 , 0 , width , height ) . clip ( Rect2i ( p_dest - src_underscan , clipped_src_rect . size ) ) ;
2014-02-10 01:10:30 +00:00
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
2017-03-05 15:44:50 +00:00
uint8_t * dst_data_ptr = wp . ptr ( ) ;
2014-02-10 01:10:30 +00:00
2017-05-17 10:36:47 +00:00
PoolVector < uint8_t > : : Read rp = p_src - > data . read ( ) ;
2017-03-05 15:44:50 +00:00
const uint8_t * src_data_ptr = rp . ptr ( ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
int pixel_size = get_format_pixel_size ( format ) ;
2014-02-10 01:10:30 +00:00
2017-06-14 13:56:58 +00:00
for ( int i = 0 ; i < dest_rect . size . y ; i + + ) {
2014-02-10 01:10:30 +00:00
2017-06-14 13:56:58 +00:00
for ( int j = 0 ; j < dest_rect . size . x ; j + + ) {
2014-02-10 01:10:30 +00:00
2017-06-14 13:56:58 +00:00
int src_x = clipped_src_rect . position . x + j ;
int src_y = clipped_src_rect . position . y + i ;
2014-02-10 01:10:30 +00:00
2017-06-14 13:56:58 +00:00
int dst_x = dest_rect . position . x + j ;
int dst_y = dest_rect . position . y + i ;
2014-02-10 01:10:30 +00:00
2017-05-17 10:36:47 +00:00
const uint8_t * src = & src_data_ptr [ ( src_y * p_src - > width + src_x ) * pixel_size ] ;
2017-03-05 15:44:50 +00:00
uint8_t * dst = & dst_data_ptr [ ( dst_y * width + dst_x ) * pixel_size ] ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( int k = 0 ; k < pixel_size ; k + + ) {
dst [ k ] = src [ k ] ;
2014-02-10 01:10:30 +00:00
}
}
}
}
2017-06-24 15:53:54 +00:00
void Image : : blit_rect_mask ( const Ref < Image > & p_src , const Ref < Image > & p_mask , const Rect2 & p_src_rect , const Point2 & p_dest ) {
2019-09-25 08:28:50 +00: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 15:53:54 +00: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 08:28:50 +00: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 15:53:54 +00:00
ERR_FAIL_COND ( format ! = p_src - > format ) ;
Rect2i clipped_src_rect = Rect2i ( 0 , 0 , p_src - > width , p_src - > height ) . clip ( p_src_rect ) ;
2018-04-15 20:37:23 +00:00
if ( p_dest . x < 0 )
clipped_src_rect . position . x = ABS ( p_dest . x ) ;
if ( p_dest . y < 0 )
clipped_src_rect . position . y = ABS ( p_dest . y ) ;
2017-06-24 15:53:54 +00:00
if ( clipped_src_rect . size . x < = 0 | | clipped_src_rect . size . y < = 0 )
return ;
2018-11-27 19:37:02 +00:00
Point2 src_underscan = Point2 ( MIN ( 0 , p_src_rect . position . x ) , MIN ( 0 , p_src_rect . position . y ) ) ;
Rect2i dest_rect = Rect2i ( 0 , 0 , width , height ) . clip ( Rect2i ( p_dest - src_underscan , clipped_src_rect . size ) ) ;
2017-06-24 15:53:54 +00:00
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
uint8_t * dst_data_ptr = wp . ptr ( ) ;
PoolVector < uint8_t > : : Read rp = p_src - > data . read ( ) ;
const uint8_t * src_data_ptr = rp . ptr ( ) ;
int pixel_size = get_format_pixel_size ( format ) ;
2017-07-08 15:12:18 +00:00
2017-06-24 15:53:54 +00:00
Ref < Image > msk = p_mask ;
msk - > lock ( ) ;
for ( int i = 0 ; i < dest_rect . size . y ; i + + ) {
for ( int j = 0 ; j < dest_rect . size . x ; j + + ) {
int src_x = clipped_src_rect . position . x + j ;
int src_y = clipped_src_rect . position . y + i ;
2017-07-08 15:12:18 +00:00
2017-06-24 15:53:54 +00: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 ] ;
}
}
}
}
msk - > unlock ( ) ;
}
2017-06-18 16:38:43 +00:00
void Image : : blend_rect ( const Ref < Image > & p_src , const Rect2 & p_src_rect , const Point2 & p_dest ) {
2019-09-25 08:28:50 +00:00
ERR_FAIL_COND_MSG ( p_src . is_null ( ) , " It's not a reference to a valid Image object. " ) ;
2017-06-18 16:38:43 +00: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 ) ;
Rect2i clipped_src_rect = Rect2i ( 0 , 0 , p_src - > width , p_src - > height ) . clip ( p_src_rect ) ;
2018-04-15 20:37:23 +00:00
if ( p_dest . x < 0 )
clipped_src_rect . position . x = ABS ( p_dest . x ) ;
if ( p_dest . y < 0 )
clipped_src_rect . position . y = ABS ( p_dest . y ) ;
2017-06-18 16:38:43 +00:00
if ( clipped_src_rect . size . x < = 0 | | clipped_src_rect . size . y < = 0 )
return ;
2018-11-27 19:37:02 +00:00
Point2 src_underscan = Point2 ( MIN ( 0 , p_src_rect . position . x ) , MIN ( 0 , p_src_rect . position . y ) ) ;
Rect2i dest_rect = Rect2i ( 0 , 0 , width , height ) . clip ( Rect2i ( p_dest - src_underscan , clipped_src_rect . size ) ) ;
2017-06-18 16:38:43 +00:00
lock ( ) ;
Ref < Image > img = p_src ;
img - > lock ( ) ;
for ( int i = 0 ; i < dest_rect . size . y ; i + + ) {
for ( int j = 0 ; j < dest_rect . size . x ; j + + ) {
int src_x = clipped_src_rect . position . x + j ;
int src_y = clipped_src_rect . position . y + i ;
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 ) ;
Color dc = get_pixel ( dst_x , dst_y ) ;
dc . r = ( double ) ( sc . a * sc . r + dc . a * ( 1.0 - sc . a ) * dc . r ) ;
dc . g = ( double ) ( sc . a * sc . g + dc . a * ( 1.0 - sc . a ) * dc . g ) ;
dc . b = ( double ) ( sc . a * sc . b + dc . a * ( 1.0 - sc . a ) * dc . b ) ;
dc . a = ( double ) ( sc . a + dc . a * ( 1.0 - sc . a ) ) ;
2017-07-07 17:05:45 +00:00
set_pixel ( dst_x , dst_y , dc ) ;
2017-06-18 16:38:43 +00:00
}
}
img - > unlock ( ) ;
unlock ( ) ;
}
void Image : : blend_rect_mask ( const Ref < Image > & p_src , const Ref < Image > & p_mask , const Rect2 & p_src_rect , const Point2 & p_dest ) {
2019-09-25 08:28:50 +00: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 16:38:43 +00: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 08:28:50 +00: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 16:38:43 +00:00
ERR_FAIL_COND ( format ! = p_src - > format ) ;
Rect2i clipped_src_rect = Rect2i ( 0 , 0 , p_src - > width , p_src - > height ) . clip ( p_src_rect ) ;
2018-04-15 20:37:23 +00:00
if ( p_dest . x < 0 )
clipped_src_rect . position . x = ABS ( p_dest . x ) ;
if ( p_dest . y < 0 )
clipped_src_rect . position . y = ABS ( p_dest . y ) ;
2017-06-18 16:38:43 +00:00
if ( clipped_src_rect . size . x < = 0 | | clipped_src_rect . size . y < = 0 )
return ;
2018-11-27 19:37:02 +00:00
Point2 src_underscan = Point2 ( MIN ( 0 , p_src_rect . position . x ) , MIN ( 0 , p_src_rect . position . y ) ) ;
Rect2i dest_rect = Rect2i ( 0 , 0 , width , height ) . clip ( Rect2i ( p_dest - src_underscan , clipped_src_rect . size ) ) ;
2017-06-18 16:38:43 +00:00
lock ( ) ;
Ref < Image > img = p_src ;
Ref < Image > msk = p_mask ;
img - > lock ( ) ;
msk - > lock ( ) ;
for ( int i = 0 ; i < dest_rect . size . y ; i + + ) {
for ( int j = 0 ; j < dest_rect . size . x ; j + + ) {
int src_x = clipped_src_rect . position . x + j ;
int src_y = clipped_src_rect . position . y + i ;
// 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 ) ;
Color dc = get_pixel ( dst_x , dst_y ) ;
dc . r = ( double ) ( sc . a * sc . r + dc . a * ( 1.0 - sc . a ) * dc . r ) ;
dc . g = ( double ) ( sc . a * sc . g + dc . a * ( 1.0 - sc . a ) * dc . g ) ;
dc . b = ( double ) ( sc . a * sc . b + dc . a * ( 1.0 - sc . a ) * dc . b ) ;
dc . a = ( double ) ( sc . a + dc . a * ( 1.0 - sc . a ) ) ;
2017-07-07 17:05:45 +00:00
set_pixel ( dst_x , dst_y , dc ) ;
2017-06-18 16:38:43 +00:00
}
}
}
msk - > unlock ( ) ;
img - > unlock ( ) ;
unlock ( ) ;
}
void Image : : fill ( const Color & c ) {
lock ( ) ;
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
uint8_t * dst_data_ptr = wp . ptr ( ) ;
int pixel_size = get_format_pixel_size ( format ) ;
// put first pixel with the format-aware API
2017-07-07 17:05:45 +00:00
set_pixel ( 0 , 0 , c ) ;
2017-06-18 16:38:43 +00:00
for ( int y = 0 ; y < height ; y + + ) {
for ( int x = 0 ; x < width ; x + + ) {
uint8_t * dst = & dst_data_ptr [ ( y * width + x ) * pixel_size ] ;
for ( int k = 0 ; k < pixel_size ; k + + ) {
dst [ k ] = dst_data_ptr [ k ] ;
}
}
}
unlock ( ) ;
}
2018-07-14 17:42:37 +00:00
ImageMemLoadFunc Image : : _png_mem_loader_func = NULL ;
ImageMemLoadFunc Image : : _jpg_mem_loader_func = NULL ;
ImageMemLoadFunc Image : : _webp_mem_loader_func = NULL ;
2014-02-10 01:10:30 +00:00
2018-08-22 16:05:25 +00:00
void ( * Image : : _image_compress_bc_func ) ( Image * , float , Image : : CompressSource ) = NULL ;
2018-08-22 02:56:04 +00:00
void ( * Image : : _image_compress_bptc_func ) ( Image * , float , Image : : CompressSource ) = NULL ;
2017-03-05 15:44:50 +00:00
void ( * Image : : _image_compress_pvrtc2_func ) ( Image * ) = NULL ;
void ( * Image : : _image_compress_pvrtc4_func ) ( Image * ) = NULL ;
2017-04-26 15:49:08 +00:00
void ( * Image : : _image_compress_etc1_func ) ( Image * , float ) = NULL ;
2017-06-17 00:47:28 +00:00
void ( * Image : : _image_compress_etc2_func ) ( Image * , float , Image : : CompressSource ) = NULL ;
2017-03-05 15:44:50 +00:00
void ( * Image : : _image_decompress_pvrtc ) ( Image * ) = NULL ;
void ( * Image : : _image_decompress_bc ) ( Image * ) = NULL ;
2018-08-22 02:56:04 +00:00
void ( * Image : : _image_decompress_bptc ) ( Image * ) = NULL ;
2017-04-26 15:49:08 +00:00
void ( * Image : : _image_decompress_etc1 ) ( Image * ) = NULL ;
2017-03-05 15:44:50 +00:00
void ( * Image : : _image_decompress_etc2 ) ( Image * ) = NULL ;
2014-02-10 01:10:30 +00:00
2017-05-17 10:36:47 +00:00
PoolVector < uint8_t > ( * Image : : lossy_packer ) ( const Ref < Image > & , float ) = NULL ;
Ref < Image > ( * Image : : lossy_unpacker ) ( const PoolVector < uint8_t > & ) = NULL ;
PoolVector < uint8_t > ( * Image : : lossless_packer ) ( const Ref < Image > & ) = NULL ;
Ref < Image > ( * Image : : lossless_unpacker ) ( const PoolVector < uint8_t > & ) = NULL ;
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 " ] ;
PoolVector < uint8_t > ddata = p_data [ " data " ] ;
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 ) ;
create ( dwidth , dheight , dmipmaps , ddformat , ddata ) ;
}
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 ;
}
2017-05-27 00:49:49 +00:00
void Image : : lock ( ) {
ERR_FAIL_COND ( data . size ( ) = = 0 ) ;
write_lock = data . write ( ) ;
}
void Image : : unlock ( ) {
2019-07-05 17:08:43 +00:00
write_lock . release ( ) ;
2017-05-27 00:49:49 +00:00
}
2018-04-29 13:17:06 +00:00
Color Image : : get_pixelv ( const Point2 & p_src ) const {
return get_pixel ( p_src . x , p_src . y ) ;
}
2017-06-17 00:28:58 +00:00
Color Image : : get_pixel ( int p_x , int p_y ) const {
2017-05-27 00:49:49 +00:00
uint8_t * ptr = write_lock . ptr ( ) ;
# ifdef DEBUG_ENABLED
2019-08-15 02:57:49 +00:00
ERR_FAIL_COND_V_MSG ( ! ptr , Color ( ) , " Image must be locked with 'lock()' before using get_pixel(). " ) ;
2017-05-27 00:49:49 +00:00
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 ;
switch ( format ) {
case FORMAT_L8 : {
float l = ptr [ ofs ] / 255.0 ;
return Color ( l , l , l , 1 ) ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_R8 : {
float r = ptr [ ofs ] / 255.0 ;
return Color ( r , 0 , 0 , 1 ) ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_RGBA4444 : {
uint16_t u = ( ( uint16_t * ) ptr ) [ ofs ] ;
float r = ( u & 0xF ) / 15.0 ;
float g = ( ( u > > 4 ) & 0xF ) / 15.0 ;
float b = ( ( u > > 8 ) & 0xF ) / 15.0 ;
float a = ( ( u > > 12 ) & 0xF ) / 15.0 ;
return Color ( r , g , b , a ) ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_RGBA5551 : {
uint16_t u = ( ( uint16_t * ) ptr ) [ ofs ] ;
float r = ( u & 0x1F ) / 15.0 ;
float g = ( ( u > > 5 ) & 0x1F ) / 15.0 ;
float b = ( ( u > > 10 ) & 0x1F ) / 15.0 ;
float a = ( ( u > > 15 ) & 0x1 ) / 1.0 ;
return Color ( r , g , b , a ) ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_RF : {
float r = ( ( float * ) ptr ) [ ofs ] ;
return Color ( r , 0 , 0 , 1 ) ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_RH : {
uint16_t r = ( ( uint16_t * ) ptr ) [ ofs ] ;
return Color ( Math : : half_to_float ( r ) , 0 , 0 , 1 ) ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00: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 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
case FORMAT_RGBE9995 : {
2018-08-22 02:56:04 +00:00
return Color : : from_rgbe9995 ( ( ( uint32_t * ) ptr ) [ ofs ] ) ;
2019-07-20 06:09:57 +00:00
}
2017-05-27 00:49:49 +00:00
default : {
2019-08-15 02:57:49 +00:00
ERR_FAIL_V_MSG ( Color ( ) , " Can't get_pixel() on compressed image, sorry. " ) ;
2017-05-27 00:49:49 +00:00
}
}
}
2018-04-29 13:17:06 +00:00
void Image : : set_pixelv ( const Point2 & p_dst , const Color & p_color ) {
2019-07-01 10:59:42 +00:00
set_pixel ( p_dst . x , p_dst . y , p_color ) ;
2018-04-29 13:17:06 +00:00
}
2017-07-07 17:05:45 +00:00
void Image : : set_pixel ( int p_x , int p_y , const Color & p_color ) {
2017-05-27 00:49:49 +00:00
uint8_t * ptr = write_lock . ptr ( ) ;
# ifdef DEBUG_ENABLED
2019-08-15 02:57:49 +00:00
ERR_FAIL_COND_MSG ( ! ptr , " Image must be locked with 'lock()' before using set_pixel(). " ) ;
2017-05-27 00:49:49 +00:00
ERR_FAIL_INDEX ( p_x , width ) ;
ERR_FAIL_INDEX ( p_y , height ) ;
# endif
uint32_t ofs = p_y * width + p_x ;
switch ( format ) {
case FORMAT_L8 : {
2018-08-21 15:47:31 +00:00
ptr [ ofs ] = uint8_t ( CLAMP ( p_color . get_v ( ) * 255.0 , 0 , 255 ) ) ;
2017-05-27 00:49:49 +00:00
} break ;
case FORMAT_LA8 : {
2018-08-21 15:47:31 +00:00
ptr [ ofs * 2 + 0 ] = uint8_t ( CLAMP ( p_color . get_v ( ) * 255.0 , 0 , 255 ) ) ;
2017-05-27 00:49:49 +00: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 ;
rgba = uint16_t ( CLAMP ( p_color . r * 15.0 , 0 , 15 ) ) ;
rgba | = uint16_t ( CLAMP ( p_color . g * 15.0 , 0 , 15 ) ) < < 4 ;
rgba | = uint16_t ( CLAMP ( p_color . b * 15.0 , 0 , 15 ) ) < < 8 ;
rgba | = uint16_t ( CLAMP ( p_color . a * 15.0 , 0 , 15 ) ) < < 12 ;
( ( uint16_t * ) ptr ) [ ofs ] = rgba ;
} break ;
case FORMAT_RGBA5551 : {
uint16_t rgba = 0 ;
rgba = uint16_t ( CLAMP ( p_color . r * 31.0 , 0 , 31 ) ) ;
rgba | = uint16_t ( CLAMP ( p_color . g * 31.0 , 0 , 31 ) ) < < 5 ;
rgba | = uint16_t ( CLAMP ( p_color . b * 31.0 , 0 , 31 ) ) < < 10 ;
rgba | = uint16_t ( p_color . a > 0.5 ? 1 : 0 ) < < 15 ;
( ( 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 00:46:48 +00:00
( ( uint32_t * ) ptr ) [ ofs ] = p_color . to_rgbe9995 ( ) ;
2017-05-27 00:49:49 +00:00
} break ;
default : {
2019-08-15 02:57:49 +00:00
ERR_FAIL_MSG ( " Can't set_pixel() on compressed image, sorry. " ) ;
2017-05-27 00:49:49 +00:00
}
}
}
Image : : DetectChannels Image : : get_detected_channels ( ) {
ERR_FAIL_COND_V ( data . size ( ) = = 0 , DETECTED_RGBA ) ;
ERR_FAIL_COND_V ( is_compressed ( ) , DETECTED_RGBA ) ;
bool r = false , g = false , b = false , a = false , c = false ;
lock ( ) ;
for ( int i = 0 ; i < width ; i + + ) {
for ( int j = 0 ; j < height ; j + + ) {
Color col = get_pixel ( i , j ) ;
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 ;
}
}
}
unlock ( ) ;
if ( ! c & & ! a )
return DETECTED_L ;
if ( ! c & & a )
return DETECTED_LA ;
if ( r & & ! g & & ! b & & ! a )
return DETECTED_R ;
if ( r & & g & & ! b & & ! a )
return DETECTED_RG ;
if ( r & & g & & b & & ! a )
return DETECTED_RGB ;
return DETECTED_RGBA ;
}
2018-08-06 17:56:06 +00:00
void Image : : optimize_channels ( ) {
switch ( get_detected_channels ( ) ) {
case DETECTED_L : convert ( FORMAT_L8 ) ; break ;
case DETECTED_LA : convert ( FORMAT_LA8 ) ; break ;
case DETECTED_R : convert ( FORMAT_R8 ) ; break ;
case DETECTED_RG : convert ( FORMAT_RG8 ) ; break ;
case DETECTED_RGB : convert ( FORMAT_RGB8 ) ; break ;
case DETECTED_RGBA : convert ( FORMAT_RGBA8 ) ; break ;
}
}
2017-05-17 10:36:47 +00: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 11:45:03 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_size " ) , & Image : : get_size ) ;
2017-05-17 10:36:47 +00: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 ) ;
ClassDB : : bind_method ( D_METHOD ( " get_mipmap_offset " , " mipmap " ) , & Image : : get_mipmap_offset ) ;
2017-06-23 01:51:36 +00:00
ClassDB : : bind_method ( D_METHOD ( " resize_to_po2 " , " square " ) , & Image : : resize_to_po2 , DEFVAL ( false ) ) ;
2017-05-17 16:45:56 +00:00
ClassDB : : bind_method ( D_METHOD ( " resize " , " width " , " height " , " interpolation " ) , & Image : : resize , DEFVAL ( INTERPOLATE_BILINEAR ) ) ;
2017-05-17 10:36:47 +00:00
ClassDB : : bind_method ( D_METHOD ( " shrink_x2 " ) , & Image : : shrink_x2 ) ;
ClassDB : : bind_method ( D_METHOD ( " expand_x2_hq2x " ) , & Image : : expand_x2_hq2x ) ;
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 00:51:37 +00:00
ClassDB : : bind_method ( D_METHOD ( " generate_mipmaps " , " renormalize " ) , & Image : : generate_mipmaps , DEFVAL ( false ) ) ;
2017-05-17 10:36:47 +00:00
ClassDB : : bind_method ( D_METHOD ( " clear_mipmaps " ) , & Image : : clear_mipmaps ) ;
ClassDB : : bind_method ( D_METHOD ( " create " , " width " , " height " , " use_mipmaps " , " format " ) , & Image : : _create_empty ) ;
ClassDB : : bind_method ( D_METHOD ( " create_from_data " , " width " , " height " , " use_mipmaps " , " format " , " data " ) , & Image : : _create_from_data ) ;
ClassDB : : bind_method ( D_METHOD ( " is_empty " ) , & Image : : empty ) ;
ClassDB : : bind_method ( D_METHOD ( " load " , " path " ) , & Image : : load ) ;
ClassDB : : bind_method ( D_METHOD ( " save_png " , " path " ) , & Image : : save_png ) ;
2019-08-03 00:07:13 +00:00
ClassDB : : bind_method ( D_METHOD ( " save_exr " , " path " , " grayscale " ) , & Image : : save_exr , DEFVAL ( false ) ) ;
2017-05-17 10:36:47 +00:00
ClassDB : : bind_method ( D_METHOD ( " detect_alpha " ) , & Image : : detect_alpha ) ;
ClassDB : : bind_method ( D_METHOD ( " is_invisible " ) , & Image : : is_invisible ) ;
2017-07-18 19:03:34 +00:00
ClassDB : : bind_method ( D_METHOD ( " compress " , " mode " , " source " , " lossy_quality " ) , & Image : : compress ) ;
2017-05-17 10:36:47 +00:00
ClassDB : : bind_method ( D_METHOD ( " decompress " ) , & Image : : decompress ) ;
ClassDB : : bind_method ( D_METHOD ( " is_compressed " ) , & Image : : is_compressed ) ;
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 ) ;
ClassDB : : bind_method ( D_METHOD ( " normalmap_to_xy " ) , & Image : : normalmap_to_xy ) ;
2018-05-30 19:14:07 +00:00
ClassDB : : bind_method ( D_METHOD ( " rgbe_to_srgb " ) , & Image : : rgbe_to_srgb ) ;
2018-02-24 00:59:02 +00:00
ClassDB : : bind_method ( D_METHOD ( " bumpmap_to_normalmap " , " bump_scale " ) , & Image : : bumpmap_to_normalmap , DEFVAL ( 1.0 ) ) ;
2017-05-17 10:36:47 +00:00
2017-08-09 11:19:41 +00: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 16:38:43 +00:00
ClassDB : : bind_method ( D_METHOD ( " fill " , " color " ) , & Image : : fill ) ;
2017-05-17 10:36:47 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_used_rect " ) , & Image : : get_used_rect ) ;
2017-08-09 11:19:41 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_rect " , " rect " ) , & Image : : get_rect ) ;
2017-05-17 10:36:47 +00:00
2017-08-09 11:19:41 +00:00
ClassDB : : bind_method ( D_METHOD ( " copy_from " , " src " ) , & Image : : copy_internals_from ) ;
2017-05-17 10:36:47 +00:00
ClassDB : : bind_method ( D_METHOD ( " _set_data " , " data " ) , & Image : : _set_data ) ;
ClassDB : : bind_method ( D_METHOD ( " _get_data " ) , & Image : : _get_data ) ;
2017-05-27 00:49:49 +00:00
ClassDB : : bind_method ( D_METHOD ( " lock " ) , & Image : : lock ) ;
ClassDB : : bind_method ( D_METHOD ( " unlock " ) , & Image : : unlock ) ;
2018-04-29 13:17:06 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_pixelv " , " src " ) , & Image : : get_pixelv ) ;
2017-05-27 00:49:49 +00:00
ClassDB : : bind_method ( D_METHOD ( " get_pixel " , " x " , " y " ) , & Image : : get_pixel ) ;
2018-04-29 13:17:06 +00:00
ClassDB : : bind_method ( D_METHOD ( " set_pixelv " , " dst " , " color " ) , & Image : : set_pixelv ) ;
ClassDB : : bind_method ( D_METHOD ( " set_pixel " , " x " , " y " , " color " ) , & Image : : set_pixel ) ;
2017-05-27 00:49:49 +00:00
2017-12-20 20:16:21 +00: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 17:42:37 +00:00
ClassDB : : bind_method ( D_METHOD ( " load_webp_from_buffer " , " buffer " ) , & Image : : load_webp_from_buffer ) ;
2017-12-20 20:16:21 +00:00
2017-05-17 10:36:47 +00:00
ADD_PROPERTY ( PropertyInfo ( Variant : : DICTIONARY , " data " , PROPERTY_HINT_NONE , " " , PROPERTY_USAGE_STORAGE ) , " _set_data " , " _get_data " ) ;
2019-01-28 14:33:56 +00:00
BIND_CONSTANT ( MAX_WIDTH ) ;
BIND_CONSTANT ( MAX_HEIGHT ) ;
2017-08-20 15:45:01 +00: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 ) ;
BIND_ENUM_CONSTANT ( FORMAT_RGBA5551 ) ;
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_PVRTC2 ) ; //pvrtc
BIND_ENUM_CONSTANT ( FORMAT_PVRTC2A ) ;
BIND_ENUM_CONSTANT ( FORMAT_PVRTC4 ) ;
BIND_ENUM_CONSTANT ( FORMAT_PVRTC4A ) ;
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 ) ;
BIND_ENUM_CONSTANT ( FORMAT_MAX ) ;
BIND_ENUM_CONSTANT ( INTERPOLATE_NEAREST ) ;
BIND_ENUM_CONSTANT ( INTERPOLATE_BILINEAR ) ;
BIND_ENUM_CONSTANT ( INTERPOLATE_CUBIC ) ;
2018-06-01 19:53:35 +00:00
BIND_ENUM_CONSTANT ( INTERPOLATE_TRILINEAR ) ;
2019-05-05 12:03:52 +00:00
BIND_ENUM_CONSTANT ( INTERPOLATE_LANCZOS ) ;
2017-08-20 15:45:01 +00: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_PVRTC2 ) ;
BIND_ENUM_CONSTANT ( COMPRESS_PVRTC4 ) ;
BIND_ENUM_CONSTANT ( COMPRESS_ETC ) ;
BIND_ENUM_CONSTANT ( COMPRESS_ETC2 ) ;
BIND_ENUM_CONSTANT ( COMPRESS_SOURCE_GENERIC ) ;
BIND_ENUM_CONSTANT ( COMPRESS_SOURCE_SRGB ) ;
BIND_ENUM_CONSTANT ( COMPRESS_SOURCE_NORMAL ) ;
2017-05-17 10:36:47 +00:00
}
2014-02-10 01:10:30 +00:00
2018-08-22 16:05:25 +00:00
void Image : : set_compress_bc_func ( void ( * p_compress_func ) ( Image * , float , CompressSource ) ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
_image_compress_bc_func = p_compress_func ;
2014-02-10 01:10:30 +00:00
}
2018-08-22 02:56:04 +00:00
void Image : : set_compress_bptc_func ( void ( * p_compress_func ) ( Image * , float , CompressSource ) ) {
_image_compress_bptc_func = p_compress_func ;
}
2014-10-03 11:58:41 +00:00
void Image : : normalmap_to_xy ( ) {
2016-10-03 19:33:42 +00:00
convert ( Image : : FORMAT_RGBA8 ) ;
2014-10-03 11:58:41 +00:00
{
2017-03-05 15:44:50 +00:00
int len = data . size ( ) / 4 ;
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
2017-03-05 15:44:50 +00:00
unsigned char * data_ptr = wp . ptr ( ) ;
2014-10-03 11:58:41 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < len ; i + + ) {
2014-10-03 11:58:41 +00:00
2017-03-05 15:44:50 +00:00
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 ] ;
2014-10-03 11:58:41 +00:00
}
}
2016-10-03 19:33:42 +00:00
convert ( Image : : FORMAT_LA8 ) ;
2014-10-03 11:58:41 +00:00
}
2018-05-30 19:14:07 +00:00
Ref < Image > Image : : rgbe_to_srgb ( ) {
if ( data . size ( ) = = 0 )
return Ref < Image > ( ) ;
ERR_FAIL_COND_V ( format ! = FORMAT_RGBE9995 , Ref < Image > ( ) ) ;
Ref < Image > new_image ;
new_image . instance ( ) ;
new_image - > create ( width , height , 0 , Image : : FORMAT_RGB8 ) ;
lock ( ) ;
new_image - > lock ( ) ;
for ( int row = 0 ; row < height ; row + + ) {
for ( int col = 0 ; col < width ; col + + ) {
new_image - > set_pixel ( col , row , get_pixel ( col , row ) . to_srgb ( ) ) ;
}
}
unlock ( ) ;
new_image - > unlock ( ) ;
if ( has_mipmaps ( ) ) {
new_image - > generate_mipmaps ( ) ;
}
return new_image ;
}
2018-02-20 22:43:58 +00:00
void Image : : bumpmap_to_normalmap ( float bump_scale ) {
ERR_FAIL_COND ( ! _can_modify ( format ) ) ;
convert ( Image : : FORMAT_RF ) ;
2018-02-24 00:59:02 +00:00
PoolVector < uint8_t > result_image ; //rgba output
result_image . resize ( width * height * 4 ) ;
2018-02-20 22:43:58 +00:00
{
PoolVector < uint8_t > : : Read rp = data . read ( ) ;
PoolVector < uint8_t > : : Write wp = result_image . write ( ) ;
2019-10-28 07:07:29 +00:00
ERR_FAIL_COND ( ! rp . ptr ( ) ) ;
2018-02-20 22:43:58 +00:00
unsigned char * write_ptr = wp . ptr ( ) ;
2018-02-24 00:59:02 +00:00
float * read_ptr = ( float * ) rp . ptr ( ) ;
2018-02-20 22:43:58 +00:00
for ( int ty = 0 ; ty < height ; ty + + ) {
2018-02-24 00:59:02 +00:00
int py = ty + 1 ;
if ( py > = height ) py - = height ;
2018-02-20 22:43:58 +00:00
for ( int tx = 0 ; tx < width ; tx + + ) {
2018-02-24 00:59:02 +00:00
int px = tx + 1 ;
if ( px > = width ) px - = width ;
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 22:43:58 +00:00
Vector3 normal = across . cross ( up ) ;
normal . normalize ( ) ;
2018-03-15 21:22:48 +00: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 00:59:02 +00:00
write_ptr [ ( ( ty * width + tx ) < < 2 ) + 3 ] = 255 ;
2018-02-20 22:43:58 +00:00
}
}
}
2018-02-24 00:59:02 +00:00
format = FORMAT_RGBA8 ;
data = result_image ;
2018-02-20 22:43:58 +00:00
}
2014-06-16 13:22:26 +00:00
void Image : : srgb_to_linear ( ) {
2017-03-05 15:44:50 +00:00
if ( data . size ( ) = = 0 )
2014-06-16 13:22:26 +00:00
return ;
2017-03-05 15:44:50 +00: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 } ;
2014-06-16 13:22:26 +00:00
2017-03-05 15:44:50 +00:00
ERR_FAIL_COND ( format ! = FORMAT_RGB8 & & format ! = FORMAT_RGBA8 ) ;
2014-06-16 13:22:26 +00:00
2017-03-05 15:44:50 +00:00
if ( format = = FORMAT_RGBA8 ) {
2014-06-16 13:22:26 +00:00
2017-03-05 15:44:50 +00:00
int len = data . size ( ) / 4 ;
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
2017-03-05 15:44:50 +00:00
unsigned char * data_ptr = wp . ptr ( ) ;
2014-06-16 13:22:26 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < len ; i + + ) {
2014-06-16 13:22:26 +00:00
2017-03-05 15:44:50 +00:00
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 ] ] ;
2014-06-16 13:22:26 +00:00
}
2017-03-05 15:44:50 +00:00
} else if ( format = = FORMAT_RGB8 ) {
2014-06-16 13:22:26 +00:00
2017-03-05 15:44:50 +00:00
int len = data . size ( ) / 3 ;
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
2017-03-05 15:44:50 +00:00
unsigned char * data_ptr = wp . ptr ( ) ;
2014-06-16 13:22:26 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < len ; i + + ) {
2014-06-16 13:22:26 +00:00
2017-03-05 15:44:50 +00:00
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-06-16 13:22:26 +00:00
}
}
}
2014-05-24 04:35:47 +00:00
void Image : : premultiply_alpha ( ) {
2017-03-05 15:44:50 +00:00
if ( data . size ( ) = = 0 )
2014-05-24 04:35:47 +00:00
return ;
2017-03-05 15:44:50 +00:00
if ( format ! = FORMAT_RGBA8 )
2014-05-24 04:35:47 +00:00
return ; //not needed
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
2017-03-05 15:44:50 +00:00
unsigned char * data_ptr = wp . ptr ( ) ;
2014-05-24 04:35:47 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < height ; i + + ) {
for ( int j = 0 ; j < width ; j + + ) {
2014-05-24 04:35:47 +00:00
2017-03-05 15:44:50 +00:00
uint8_t * ptr = & data_ptr [ ( i * width + j ) * 4 ] ;
2014-05-24 04:35:47 +00:00
2017-03-05 15:44:50 +00:00
ptr [ 0 ] = ( uint16_t ( ptr [ 0 ] ) * uint16_t ( ptr [ 3 ] ) ) > > 8 ;
ptr [ 1 ] = ( uint16_t ( ptr [ 1 ] ) * uint16_t ( ptr [ 3 ] ) ) > > 8 ;
ptr [ 2 ] = ( uint16_t ( ptr [ 2 ] ) * uint16_t ( ptr [ 3 ] ) ) > > 8 ;
2014-05-24 04:35:47 +00:00
}
}
}
2014-02-10 01:10:30 +00:00
void Image : : fix_alpha_edges ( ) {
2017-03-05 15:44:50 +00:00
if ( data . size ( ) = = 0 )
2014-02-10 01:10:30 +00:00
return ;
2017-03-05 15:44:50 +00:00
if ( format ! = FORMAT_RGBA8 )
2014-02-10 01:10:30 +00:00
return ; //not needed
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > dcopy = data ;
2017-02-01 12:45:45 +00:00
PoolVector < uint8_t > : : Read rp = dcopy . read ( ) ;
2017-03-05 15:44:50 +00:00
const uint8_t * srcptr = rp . ptr ( ) ;
2014-02-10 01:10:30 +00:00
2017-01-07 21:25:37 +00:00
PoolVector < uint8_t > : : Write wp = data . write ( ) ;
2017-03-05 15:44:50 +00:00
unsigned char * data_ptr = wp . ptr ( ) ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
const int max_radius = 4 ;
2017-07-08 15:12:18 +00:00
const int alpha_threshold = 20 ;
2017-03-05 15:44:50 +00:00
const int max_dist = 0x7FFFFFFF ;
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
for ( int i = 0 ; i < height ; i + + ) {
for ( int j = 0 ; j < width ; j + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
const uint8_t * rptr = & srcptr [ ( i * width + j ) * 4 ] ;
uint8_t * wptr = & data_ptr [ ( i * width + j ) * 4 ] ;
2016-10-03 19:33:42 +00:00
2017-07-08 15:12:18 +00:00
if ( rptr [ 3 ] > = alpha_threshold )
2014-02-10 01:10:30 +00:00
continue ;
2017-03-05 15:44:50 +00:00
int closest_dist = max_dist ;
2016-10-03 19:33:42 +00:00
uint8_t closest_color [ 3 ] ;
2017-03-05 15:44:50 +00: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 19:33:42 +00:00
2017-03-05 15:44:50 +00:00
for ( int k = from_y ; k < = to_y ; k + + ) {
for ( int l = from_x ; l < = to_x ; l + + ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
int dy = i - k ;
int dx = j - l ;
int dist = dy * dy + dx * dx ;
if ( dist > = closest_dist )
2014-02-10 01:10:30 +00:00
continue ;
2019-02-12 20:10:08 +00:00
const uint8_t * rp2 = & srcptr [ ( k * width + l ) < < 2 ] ;
2014-02-10 01:10:30 +00:00
2019-02-12 20:10:08 +00:00
if ( rp2 [ 3 ] < alpha_threshold )
2014-02-10 01:10:30 +00:00
continue ;
2017-10-13 13:03:05 +00:00
closest_dist = dist ;
2019-02-12 20:10:08 +00:00
closest_color [ 0 ] = rp2 [ 0 ] ;
closest_color [ 1 ] = rp2 [ 1 ] ;
closest_color [ 2 ] = rp2 [ 2 ] ;
2014-02-10 01:10:30 +00:00
}
}
2017-03-05 15:44:50 +00:00
if ( closest_dist ! = max_dist ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
wptr [ 0 ] = closest_color [ 0 ] ;
wptr [ 1 ] = closest_color [ 1 ] ;
wptr [ 2 ] = closest_color [ 2 ] ;
2016-10-03 19:33:42 +00:00
}
2014-02-10 01:10:30 +00:00
}
}
}
2015-10-21 12:50:44 +00:00
String Image : : get_format_name ( Format p_format ) {
2017-03-05 15:44:50 +00:00
ERR_FAIL_INDEX_V ( p_format , FORMAT_MAX , String ( ) ) ;
2015-10-21 12:50:44 +00:00
return format_names [ p_format ] ;
}
2017-12-20 20:16:21 +00:00
Error Image : : load_png_from_buffer ( const PoolVector < uint8_t > & p_array ) {
2018-07-14 17:42:37 +00:00
return _load_from_buffer ( p_array , _png_mem_loader_func ) ;
2017-12-20 20:16:21 +00:00
}
Error Image : : load_jpg_from_buffer ( const PoolVector < uint8_t > & p_array ) {
2018-07-14 17:42:37 +00:00
return _load_from_buffer ( p_array , _jpg_mem_loader_func ) ;
}
Error Image : : load_webp_from_buffer ( const PoolVector < uint8_t > & p_array ) {
return _load_from_buffer ( p_array , _webp_mem_loader_func ) ;
}
2017-12-20 20:16:21 +00:00
2018-07-14 17:42:37 +00:00
Error Image : : _load_from_buffer ( const PoolVector < uint8_t > & p_array , ImageMemLoadFunc p_loader ) {
2017-12-20 20:16:21 +00:00
int buffer_size = p_array . size ( ) ;
ERR_FAIL_COND_V ( buffer_size = = 0 , ERR_INVALID_PARAMETER ) ;
2018-07-14 17:42:37 +00:00
ERR_FAIL_COND_V ( ! p_loader , ERR_INVALID_PARAMETER ) ;
2017-12-20 20:16:21 +00:00
PoolVector < uint8_t > : : Read r = p_array . read ( ) ;
2018-07-14 17:42:37 +00:00
Ref < Image > image = p_loader ( r . ptr ( ) , buffer_size ) ;
2017-12-20 20:16:21 +00:00
ERR_FAIL_COND_V ( ! image . is_valid ( ) , ERR_PARSE_ERROR ) ;
copy_internals_from ( image ) ;
return OK ;
}
2018-08-22 02:56:04 +00: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
}
2017-03-05 15:44:50 +00:00
Image : : Image ( const uint8_t * p_mem_png_jpg , int p_len ) {
2014-02-10 01:10:30 +00:00
2017-03-05 15:44:50 +00:00
width = 0 ;
height = 0 ;
mipmaps = false ;
format = FORMAT_L8 ;
2014-02-10 01:10:30 +00:00
if ( _png_mem_loader_func ) {
2017-05-17 10:36:47 +00:00
copy_internals_from ( _png_mem_loader_func ( p_mem_png_jpg , p_len ) ) ;
2016-03-12 13:44:12 +00:00
}
if ( empty ( ) & & _jpg_mem_loader_func ) {
2017-05-17 10:36:47 +00:00
copy_internals_from ( _jpg_mem_loader_func ( p_mem_png_jpg , p_len ) ) ;
2014-02-10 01:10:30 +00:00
}
}
2017-05-17 10:36:47 +00:00
Ref < Resource > Image : : duplicate ( bool p_subresources ) const {
Ref < Image > copy ;
copy . instance ( ) ;
copy - > _copy_internals_from ( * this ) ;
return copy ;
}
2014-02-10 01:10:30 +00:00
Image : : Image ( ) {
2017-03-05 15:44:50 +00:00
width = 0 ;
height = 0 ;
mipmaps = false ;
2016-10-03 19:33:42 +00:00
format = FORMAT_L8 ;
2014-02-10 01:10:30 +00:00
}
Image : : ~ Image ( ) {
2017-05-27 00:49:49 +00:00
if ( write_lock . ptr ( ) ) {
unlock ( ) ;
}
2014-02-10 01:10:30 +00:00
}