2019-07-24 21:39:02 +00:00
import hashlib
2019-05-08 21:06:42 +00:00
import io
import numpy
import numpy . core . multiarray # important this comes before cv!
2020-07-29 20:52:44 +00:00
import struct
import warnings
2019-09-05 00:05:32 +00:00
try :
2020-05-13 19:03:16 +00:00
# more hidden imports for pyinstaller
import numpy . random . common # pylint: disable=E0401
import numpy . random . bounded_integers # pylint: disable=E0401
import numpy . random . entropy # pylint: disable=E0401
2019-09-05 00:05:32 +00:00
except :
pass # old version of numpy, screw it
2014-10-22 22:31:58 +00:00
from PIL import _imaging
2017-11-08 22:07:12 +00:00
from PIL import ImageFile as PILImageFile
2013-02-19 00:11:43 +00:00
from PIL import Image as PILImage
2021-12-01 22:12:16 +00:00
from PIL import ImageCms as PILImageCms
2020-07-29 20:52:44 +00:00
from hydrus . core import HydrusConstants as HC
2020-04-22 21:00:35 +00:00
from hydrus . core import HydrusData
2020-07-29 20:52:44 +00:00
from hydrus . core import HydrusExceptions
2020-04-22 21:00:35 +00:00
from hydrus . core import HydrusGlobals as HG
from hydrus . core import HydrusPaths
2021-10-27 21:12:33 +00:00
from hydrus . core import HydrusTemp
2017-10-04 17:51:58 +00:00
2021-12-01 22:12:16 +00:00
PIL_SRGB_PROFILE = PILImageCms . createProfile ( ' sRGB ' )
2019-09-25 21:34:18 +00:00
def EnableLoadTruncatedImages ( ) :
if hasattr ( PILImageFile , ' LOAD_TRUNCATED_IMAGES ' ) :
# this can now cause load hangs due to the trunc load code adding infinite fake EOFs to the file stream, wew lad
# hence debug only
PILImageFile . LOAD_TRUNCATED_IMAGES = True
return True
else :
return False
2017-11-08 22:07:12 +00:00
2020-05-27 21:27:52 +00:00
if not hasattr ( PILImage , ' DecompressionBombError ' ) :
# super old versions don't have this, so let's just make a stub, wew
class DBE_stub ( Exception ) :
pass
PILImage . DecompressionBombError = DBE_stub
2017-10-11 17:38:14 +00:00
if not hasattr ( PILImage , ' DecompressionBombWarning ' ) :
# super old versions don't have this, so let's just make a stub, wew
class DBW_stub ( Exception ) :
pass
PILImage . DecompressionBombWarning = DBW_stub
2017-10-04 17:51:58 +00:00
warnings . simplefilter ( ' ignore ' , PILImage . DecompressionBombWarning )
2020-05-27 21:27:52 +00:00
warnings . simplefilter ( ' ignore ' , PILImage . DecompressionBombError )
2013-02-19 00:11:43 +00:00
2018-04-11 22:30:40 +00:00
OLD_PIL_MAX_IMAGE_PIXELS = PILImage . MAX_IMAGE_PIXELS
PILImage . MAX_IMAGE_PIXELS = None # this turns off decomp check entirely, wew
2019-05-15 20:35:00 +00:00
PIL_ONLY_MIMETYPES = { HC . IMAGE_GIF , HC . IMAGE_ICON }
2019-05-08 21:06:42 +00:00
try :
import cv2
if cv2 . __version__ . startswith ( ' 2 ' ) :
CV_IMREAD_FLAGS_SUPPORTS_ALPHA = cv2 . CV_LOAD_IMAGE_UNCHANGED
CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION = CV_IMREAD_FLAGS_SUPPORTS_ALPHA
# there's something wrong with these, but I don't have an easy test env for it atm
# CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION = cv2.CV_LOAD_IMAGE_ANYDEPTH | cv2.CV_LOAD_IMAGE_ANYCOLOR
CV_JPEG_THUMBNAIL_ENCODE_PARAMS = [ ]
CV_PNG_THUMBNAIL_ENCODE_PARAMS = [ ]
else :
CV_IMREAD_FLAGS_SUPPORTS_ALPHA = cv2 . IMREAD_UNCHANGED
CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION = cv2 . IMREAD_ANYDEPTH | cv2 . IMREAD_ANYCOLOR # this preserves colour info but does EXIF reorientation and flipping
CV_JPEG_THUMBNAIL_ENCODE_PARAMS = [ cv2 . IMWRITE_JPEG_QUALITY , 92 ]
CV_PNG_THUMBNAIL_ENCODE_PARAMS = [ cv2 . IMWRITE_PNG_COMPRESSION , 9 ]
OPENCV_OK = True
except :
OPENCV_OK = False
2020-06-24 21:25:24 +00:00
def ConvertToPNGIfBMP ( path ) :
2013-02-19 00:11:43 +00:00
2017-07-19 21:21:41 +00:00
with open ( path , ' rb ' ) as f :
header = f . read ( 2 )
2013-08-07 22:25:18 +00:00
2019-01-09 22:59:03 +00:00
if header == b ' BM ' :
2013-02-19 00:11:43 +00:00
2021-10-27 21:12:33 +00:00
( os_file_handle , temp_path ) = HydrusTemp . GetTempPath ( )
2013-02-19 00:11:43 +00:00
2015-03-25 22:04:19 +00:00
try :
with open ( path , ' rb ' ) as f_source :
with open ( temp_path , ' wb ' ) as f_dest :
2015-11-04 22:30:28 +00:00
HydrusPaths . CopyFileLikeToFileLike ( f_source , f_dest )
2015-03-25 22:04:19 +00:00
pil_image = GeneratePILImage ( temp_path )
pil_image . save ( path , ' PNG ' )
finally :
2021-10-27 21:12:33 +00:00
HydrusTemp . CleanUpTempPath ( os_file_handle , temp_path )
2015-03-25 22:04:19 +00:00
2013-02-19 00:11:43 +00:00
2021-12-01 22:12:16 +00:00
def DequantizeNumPyImage ( numpy_image : numpy . array ) - > numpy . array :
# OpenCV loads images in BGR, and we want to normalise to RGB in general
if numpy_image . dtype == ' uint16 ' :
numpy_image = numpy . array ( numpy_image / / 256 , dtype = ' uint8 ' )
shape = numpy_image . shape
2016-06-29 19:55:46 +00:00
2021-12-01 22:12:16 +00:00
if len ( shape ) == 2 :
# monochrome image
convert = cv2 . COLOR_GRAY2RGB
else :
2016-06-29 19:55:46 +00:00
2021-12-01 22:12:16 +00:00
( im_y , im_x , depth ) = shape
if depth == 4 :
2016-06-29 19:55:46 +00:00
2021-12-01 22:12:16 +00:00
convert = cv2 . COLOR_BGRA2RGBA
2016-06-29 19:55:46 +00:00
else :
2021-12-01 22:12:16 +00:00
convert = cv2 . COLOR_BGR2RGB
numpy_image = cv2 . cvtColor ( numpy_image , convert )
return numpy_image
def DequantizePILImage ( pil_image : PILImage . Image ) - > PILImage . Image :
if HasICCProfile ( pil_image ) :
try :
pil_image = NormaliseICCProfilePILImageToSRGB ( pil_image )
except Exception as e :
HydrusData . ShowException ( e )
HydrusData . ShowText ( ' Failed to normalise image ICC profile. ' )
2016-06-29 19:55:46 +00:00
2021-12-01 22:12:16 +00:00
if PILImageHasAlpha ( pil_image ) :
desired_mode = ' RGBA '
else :
desired_mode = ' RGB '
if pil_image . mode != desired_mode :
pil_image = pil_image . convert ( desired_mode )
2016-06-29 19:55:46 +00:00
return pil_image
2021-12-01 22:12:16 +00:00
def GenerateNumPyImage ( path , mime , force_pil = False ) - > numpy . array :
2019-05-08 21:06:42 +00:00
if HG . media_load_report_mode :
HydrusData . ShowText ( ' Loading media: ' + path )
if not OPENCV_OK :
force_pil = True
2021-12-01 22:12:16 +00:00
if not force_pil :
pil_image = RawOpenPILImage ( path )
if HG . media_load_report_mode :
HydrusData . ShowText ( ' Image has ICC, so switching to PIL ' )
if HasICCProfile ( pil_image ) :
force_pil = True
2019-05-15 20:35:00 +00:00
if mime in PIL_ONLY_MIMETYPES or force_pil :
2019-05-08 21:06:42 +00:00
if HG . media_load_report_mode :
HydrusData . ShowText ( ' Loading with PIL ' )
pil_image = GeneratePILImage ( path )
numpy_image = GenerateNumPyImageFromPILImage ( pil_image )
else :
if HG . media_load_report_mode :
HydrusData . ShowText ( ' Loading with OpenCV ' )
if mime == HC . IMAGE_JPEG :
flags = CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION
else :
flags = CV_IMREAD_FLAGS_SUPPORTS_ALPHA
numpy_image = cv2 . imread ( path , flags = flags )
2021-12-01 22:12:16 +00:00
if numpy_image is None : # doesn't support some random stuff
2019-05-08 21:06:42 +00:00
if HG . media_load_report_mode :
HydrusData . ShowText ( ' OpenCV Failed, loading with PIL ' )
pil_image = GeneratePILImage ( path )
numpy_image = GenerateNumPyImageFromPILImage ( pil_image )
else :
2021-12-01 22:12:16 +00:00
numpy_image = DequantizeNumPyImage ( numpy_image )
2019-05-08 21:06:42 +00:00
return numpy_image
2021-12-01 22:12:16 +00:00
def GenerateNumPyImageFromPILImage ( pil_image : PILImage . Image ) - > numpy . array :
2019-05-08 21:06:42 +00:00
( w , h ) = pil_image . size
s = pil_image . tobytes ( )
2021-12-01 22:12:16 +00:00
depth = len ( s ) / / ( w * h )
2019-05-08 21:06:42 +00:00
2021-12-01 22:12:16 +00:00
return numpy . fromstring ( s , dtype = ' uint8 ' ) . reshape ( ( h , w , depth ) )
2015-10-21 21:53:10 +00:00
2021-12-01 22:12:16 +00:00
def GeneratePILImage ( path , dequantize = True ) - > PILImage . Image :
2015-10-21 21:53:10 +00:00
2021-12-01 22:12:16 +00:00
pil_image = RawOpenPILImage ( path )
if pil_image is None :
2017-11-01 20:37:39 +00:00
2021-12-01 22:12:16 +00:00
raise Exception ( ' The file at {} could not be rendered! ' . format ( path ) )
2017-11-01 20:37:39 +00:00
2021-12-01 22:12:16 +00:00
RotateEXIFPILImage ( pil_image )
if dequantize :
2015-10-21 21:53:10 +00:00
2021-12-01 22:12:16 +00:00
# note this destroys animated gifs atm, it collapses down to one frame
pil_image = DequantizePILImage ( pil_image )
2015-10-21 21:53:10 +00:00
return pil_image
2021-12-01 22:12:16 +00:00
def GeneratePILImageFromNumPyImage ( numpy_image : numpy . array ) - > PILImage . Image :
2014-06-25 20:37:06 +00:00
2021-12-01 22:12:16 +00:00
# I'll leave this here as a neat artifact, but I really shouldn't ever be making a PIL from a cv2 image. the only PIL benefits are the .info dict, which this won't generate
2014-06-25 20:37:06 +00:00
2021-12-01 22:12:16 +00:00
if len ( numpy_image . shape ) == 2 :
( h , w ) = numpy_image . shape
2016-10-12 21:52:50 +00:00
2021-12-01 22:12:16 +00:00
format = ' L '
else :
2016-10-12 21:52:50 +00:00
2021-12-01 22:12:16 +00:00
( h , w , depth ) = numpy_image . shape
2016-10-12 21:52:50 +00:00
2021-12-01 22:12:16 +00:00
if depth == 1 :
format = ' L '
elif depth == 2 :
format = ' LA '
elif depth == 3 :
format = ' RGB '
elif depth == 4 :
format = ' RGBA '
2016-10-12 21:52:50 +00:00
2014-06-25 20:37:06 +00:00
2019-01-09 22:59:03 +00:00
pil_image = PILImage . frombytes ( format , ( w , h ) , numpy_image . data . tobytes ( ) )
2014-06-25 20:37:06 +00:00
return pil_image
2021-12-01 22:12:16 +00:00
def GenerateThumbnailBytesFromStaticImagePath ( path , target_resolution , mime ) - > bytes :
2019-05-08 21:06:42 +00:00
2019-05-15 20:35:00 +00:00
if OPENCV_OK :
2019-05-08 21:06:42 +00:00
numpy_image = GenerateNumPyImage ( path , mime )
thumbnail_numpy_image = ResizeNumPyImage ( numpy_image , target_resolution )
try :
thumbnail_bytes = GenerateThumbnailBytesNumPy ( thumbnail_numpy_image , mime )
return thumbnail_bytes
except HydrusExceptions . CantRenderWithCVException :
2021-12-01 22:12:16 +00:00
pass # fallback to PIL
2019-05-08 21:06:42 +00:00
pil_image = GeneratePILImage ( path )
thumbnail_pil_image = pil_image . resize ( target_resolution , PILImage . ANTIALIAS )
thumbnail_bytes = GenerateThumbnailBytesPIL ( pil_image , mime )
return thumbnail_bytes
2021-12-01 22:12:16 +00:00
def GenerateThumbnailBytesNumPy ( numpy_image , mime ) - > bytes :
2019-05-08 21:06:42 +00:00
2021-05-12 20:49:20 +00:00
( im_height , im_width , depth ) = numpy_image . shape
2019-05-08 21:06:42 +00:00
if depth == 4 :
convert = cv2 . COLOR_RGBA2BGRA
else :
convert = cv2 . COLOR_RGB2BGR
numpy_image = cv2 . cvtColor ( numpy_image , convert )
2021-12-01 22:12:16 +00:00
if mime == HC . IMAGE_PNG or depth == 4 :
2019-05-08 21:06:42 +00:00
2021-12-01 22:12:16 +00:00
ext = ' .png '
2019-05-08 21:06:42 +00:00
2021-12-01 22:12:16 +00:00
params = CV_PNG_THUMBNAIL_ENCODE_PARAMS
2019-05-08 21:06:42 +00:00
else :
2021-12-01 22:12:16 +00:00
ext = ' .jpg '
2019-05-08 21:06:42 +00:00
2021-12-01 22:12:16 +00:00
params = CV_JPEG_THUMBNAIL_ENCODE_PARAMS
2019-05-08 21:06:42 +00:00
( result_success , result_byte_array ) = cv2 . imencode ( ext , numpy_image , params )
if result_success :
thumbnail_bytes = result_byte_array . tostring ( )
return thumbnail_bytes
else :
raise HydrusExceptions . CantRenderWithCVException ( ' Thumb failed to encode! ' )
2021-12-01 22:12:16 +00:00
def GenerateThumbnailBytesPIL ( pil_image : PILImage . Image , mime ) - > bytes :
2019-05-08 21:06:42 +00:00
f = io . BytesIO ( )
if mime == HC . IMAGE_PNG or pil_image . mode == ' RGBA ' :
pil_image . save ( f , ' PNG ' )
else :
pil_image . save ( f , ' JPEG ' , quality = 92 )
f . seek ( 0 )
thumbnail_bytes = f . read ( )
f . close ( )
return thumbnail_bytes
2014-05-28 21:03:24 +00:00
def GetGIFFrameDurations ( path ) :
2021-12-01 22:12:16 +00:00
pil_image = RawOpenPILImage ( path )
2014-05-07 22:42:30 +00:00
2020-02-26 22:28:52 +00:00
times_to_play_gif = GetTimesToPlayGIFFromPIL ( pil_image )
2014-05-07 22:42:30 +00:00
frame_durations = [ ]
i = 0
while True :
2019-03-20 21:22:10 +00:00
try :
pil_image . seek ( i )
except :
break
2014-05-07 22:42:30 +00:00
2015-10-21 21:53:10 +00:00
if ' duration ' not in pil_image . info :
2019-01-09 22:59:03 +00:00
duration = 83 # (83ms -- 1000 / 12) Set a 12 fps default when duration is missing or too funky to extract. most stuff looks ok at this.
2015-10-21 21:53:10 +00:00
2014-05-07 22:42:30 +00:00
else :
2015-10-21 21:53:10 +00:00
duration = pil_image . info [ ' duration ' ]
2014-05-07 22:42:30 +00:00
2015-10-21 21:53:10 +00:00
# In the gif frame header, 10 is stored as 1ms. This 1 is commonly as utterly wrong as 0.
if duration in ( 0 , 10 ) :
2019-01-09 22:59:03 +00:00
duration = 83
2015-10-21 21:53:10 +00:00
2014-05-07 22:42:30 +00:00
frame_durations . append ( duration )
i + = 1
2020-02-26 22:28:52 +00:00
return ( frame_durations , times_to_play_gif )
2014-05-07 22:42:30 +00:00
2021-12-01 22:12:16 +00:00
def GetICCProfileBytes ( pil_image : PILImage . Image ) - > bytes :
if HasICCProfile ( pil_image ) :
return pil_image . info [ ' icc_profile ' ]
raise HydrusExceptions . DataMissing ( ' This image has no ICC profile! ' )
def GetImagePixelHash ( path , mime ) - > bytes :
2019-07-24 21:39:02 +00:00
numpy_image = GenerateNumPyImage ( path , mime )
return hashlib . sha256 ( numpy_image . data . tobytes ( ) ) . digest ( )
2018-09-26 19:05:12 +00:00
def GetImageProperties ( path , mime ) :
2014-05-14 20:46:38 +00:00
2019-05-15 20:35:00 +00:00
if OPENCV_OK and mime not in PIL_ONLY_MIMETYPES : # webp here too maybe eventually, or offload it all to ffmpeg
2014-05-14 20:46:38 +00:00
2019-05-08 21:06:42 +00:00
numpy_image = GenerateNumPyImage ( path , mime )
2014-05-14 20:46:38 +00:00
2019-05-08 21:06:42 +00:00
( width , height ) = GetResolutionNumPy ( numpy_image )
2014-05-14 20:46:38 +00:00
duration = None
num_frames = None
2019-05-08 21:06:42 +00:00
else :
( ( width , height ) , num_frames ) = GetResolutionAndNumFramesPIL ( path , mime )
if num_frames > 1 :
2020-02-26 22:28:52 +00:00
( durations , times_to_play_gif ) = GetGIFFrameDurations ( path )
2019-05-08 21:06:42 +00:00
duration = sum ( durations )
else :
duration = None
num_frames = None
2014-05-14 20:46:38 +00:00
return ( ( width , height ) , duration , num_frames )
2019-05-15 20:35:00 +00:00
# bigger number is worse quality
# this is very rough and misses some finesse
def GetJPEGQuantizationQualityEstimate ( path ) :
2021-12-01 22:12:16 +00:00
pil_image = RawOpenPILImage ( path )
2019-05-15 20:35:00 +00:00
if hasattr ( pil_image , ' quantization ' ) :
table_arrays = list ( pil_image . quantization . values ( ) )
2019-06-19 22:08:48 +00:00
if len ( table_arrays ) == 0 :
return ( ' unknown ' , None )
2019-05-15 20:35:00 +00:00
quality = sum ( ( sum ( table_array ) for table_array in table_arrays ) )
quality / = len ( table_arrays )
if quality > = 3400 :
label = ' very low '
elif quality > = 2000 :
label = ' low '
elif quality > = 1400 :
2019-06-05 19:42:39 +00:00
label = ' medium low '
2019-05-15 20:35:00 +00:00
elif quality > = 1000 :
label = ' medium '
elif quality > = 700 :
2019-06-05 19:42:39 +00:00
label = ' medium high '
2019-05-15 20:35:00 +00:00
elif quality > = 400 :
label = ' high '
elif quality > = 200 :
label = ' very high '
else :
label = ' extremely high '
return ( label , quality )
return ( ' unknown ' , None )
2019-03-20 21:22:10 +00:00
def GetPSDResolution ( path ) :
with open ( path , ' rb ' ) as f :
f . seek ( 14 )
height_bytes = f . read ( 4 )
width_bytes = f . read ( 4 )
height = struct . unpack ( ' >L ' , height_bytes ) [ 0 ]
width = struct . unpack ( ' >L ' , width_bytes ) [ 0 ]
return ( width , height )
2019-05-08 21:06:42 +00:00
def GetResolutionNumPy ( numpy_image ) :
( image_height , image_width , depth ) = numpy_image . shape
return ( image_width , image_height )
def GetResolutionAndNumFramesPIL ( path , mime ) :
2014-05-14 20:46:38 +00:00
2021-12-01 22:12:16 +00:00
pil_image = GeneratePILImage ( path , dequantize = False )
2014-05-14 20:46:38 +00:00
( x , y ) = pil_image . size
2013-02-19 00:11:43 +00:00
2018-09-26 19:05:12 +00:00
if mime == HC . IMAGE_GIF : # some jpegs came up with 2 frames and 'duration' because of some embedded thumbnail in the metadata
2013-02-19 00:11:43 +00:00
2018-09-26 19:05:12 +00:00
try :
pil_image . seek ( 1 )
pil_image . seek ( 0 )
num_frames = 1
2014-05-14 20:46:38 +00:00
2018-09-26 19:05:12 +00:00
while True :
2014-05-14 20:46:38 +00:00
2018-09-26 19:05:12 +00:00
try :
pil_image . seek ( pil_image . tell ( ) + 1 )
num_frames + = 1
2021-12-01 22:12:16 +00:00
except :
break
2014-05-14 20:46:38 +00:00
2018-09-26 19:05:12 +00:00
except :
num_frames = 1
2014-05-14 20:46:38 +00:00
2013-02-19 00:11:43 +00:00
2018-09-26 19:05:12 +00:00
else :
2017-08-23 21:34:25 +00:00
num_frames = 1
2014-05-14 20:46:38 +00:00
return ( ( x , y ) , num_frames )
2019-05-08 21:06:42 +00:00
def GetThumbnailResolution ( image_resolution , bounding_dimensions ) :
2019-01-09 22:59:03 +00:00
2019-04-03 22:45:57 +00:00
( im_width , im_height ) = image_resolution
2019-05-08 21:06:42 +00:00
( bounding_width , bounding_height ) = bounding_dimensions
2014-05-14 20:46:38 +00:00
2019-04-03 22:45:57 +00:00
if bounding_width > = im_width and bounding_height > = im_height :
2016-06-01 20:04:15 +00:00
2019-04-03 22:45:57 +00:00
return ( im_width , im_height )
2016-06-01 20:04:15 +00:00
2019-04-03 22:45:57 +00:00
width_ratio = im_width / bounding_width
height_ratio = im_height / bounding_height
thumbnail_width = bounding_width
thumbnail_height = bounding_height
2014-05-21 21:37:35 +00:00
2019-04-03 22:45:57 +00:00
if width_ratio > height_ratio :
2016-06-01 20:04:15 +00:00
2019-04-03 22:45:57 +00:00
thumbnail_height = im_height / width_ratio
2016-06-01 20:04:15 +00:00
2019-04-03 22:45:57 +00:00
elif height_ratio > width_ratio :
2016-06-01 20:04:15 +00:00
2019-04-03 22:45:57 +00:00
thumbnail_width = im_width / height_ratio
2016-06-01 20:04:15 +00:00
2013-02-19 00:11:43 +00:00
2019-04-03 22:45:57 +00:00
thumbnail_width = max ( int ( thumbnail_width ) , 1 )
thumbnail_height = max ( int ( thumbnail_height ) , 1 )
2014-05-14 20:46:38 +00:00
2019-04-03 22:45:57 +00:00
return ( thumbnail_width , thumbnail_height )
2017-01-25 22:56:55 +00:00
2021-12-01 22:12:16 +00:00
def GetTimesToPlayGIF ( path ) - > int :
2020-02-26 22:28:52 +00:00
2021-12-01 22:12:16 +00:00
pil_image = RawOpenPILImage ( path )
2020-02-26 22:28:52 +00:00
return GetTimesToPlayGIFFromPIL ( pil_image )
2021-12-01 22:12:16 +00:00
def GetTimesToPlayGIFFromPIL ( pil_image : PILImage . Image ) - > int :
2020-02-26 22:28:52 +00:00
if ' loop ' in pil_image . info :
times_to_play_gif = pil_image . info [ ' loop ' ]
else :
times_to_play_gif = 1
return times_to_play_gif
2021-12-01 22:12:16 +00:00
def HasICCProfile ( pil_image : PILImage . Image ) - > bool :
if ' icc_profile ' in pil_image . info :
icc_profile = pil_image . info [ ' icc_profile ' ]
if isinstance ( icc_profile , bytes ) and len ( icc_profile ) > 0 :
return True
return False
def IsDecompressionBomb ( path ) - > bool :
2017-10-04 17:51:58 +00:00
2020-05-27 21:27:52 +00:00
# there are two errors here, the 'Warning' and the 'Error', which atm is just a test vs a test x 2 for number of pixels
# 256MB bmp by default, ( 1024 ** 3 ) // 4 // 3
# we'll set it at 512MB, and now catching error should be about 1GB
2021-12-01 22:12:16 +00:00
PILImage . MAX_IMAGE_PIXELS = ( 512 * ( 1024 * * 2 ) ) / / 3
2018-04-11 22:30:40 +00:00
2020-05-27 21:27:52 +00:00
warnings . simplefilter ( ' error ' , PILImage . DecompressionBombError )
2017-10-04 17:51:58 +00:00
try :
2021-12-01 22:12:16 +00:00
RawOpenPILImage ( path )
2017-10-04 17:51:58 +00:00
2020-05-27 21:27:52 +00:00
except ( PILImage . DecompressionBombError ) :
2017-10-04 17:51:58 +00:00
return True
2020-05-27 21:27:52 +00:00
except :
# pil was unable to load it, which does not mean it was a decomp bomb
return False
2017-10-04 17:51:58 +00:00
finally :
2018-04-11 22:30:40 +00:00
PILImage . MAX_IMAGE_PIXELS = None
2020-05-27 21:27:52 +00:00
warnings . simplefilter ( ' ignore ' , PILImage . DecompressionBombError )
2017-10-04 17:51:58 +00:00
return False
2021-12-01 22:12:16 +00:00
def NormaliseICCProfilePILImageToSRGB ( pil_image : PILImage . Image ) :
try :
icc_profile_bytes = GetICCProfileBytes ( pil_image )
except HydrusExceptions . DataMissing :
return pil_image
try :
f = io . BytesIO ( icc_profile_bytes )
src_profile = PILImageCms . ImageCmsProfile ( f )
if PILImageHasAlpha ( pil_image ) :
outputMode = ' RGBA '
else :
outputMode = ' RGB '
pil_image = PILImageCms . profileToProfile ( pil_image , src_profile , PIL_SRGB_PROFILE , outputMode = outputMode )
except PILImageCms . PyCMSError :
# 'cannot build transform' and presumably some other fun errors
# way more advanced that we can deal with, so we'll just no-op
pass
return pil_image
def PILImageHasAlpha ( pil_image : PILImage . Image ) :
return pil_image . mode in ( ' LA ' , ' RGBA ' ) or ( pil_image . mode == ' P ' and ' transparency ' in pil_image . info )
def RawOpenPILImage ( path ) - > PILImage . Image :
try :
pil_image = PILImage . open ( path )
except Exception as e :
raise HydrusExceptions . DamagedOrUnusualFileException ( ' Could not load the image--it was likely malformed! ' )
return pil_image
def ResizeNumPyImage ( numpy_image : numpy . array , target_resolution ) - > numpy . array :
2019-05-08 21:06:42 +00:00
( target_width , target_height ) = target_resolution
( image_width , image_height ) = GetResolutionNumPy ( numpy_image )
2019-03-27 22:01:02 +00:00
2019-05-08 21:06:42 +00:00
if target_width == image_width and target_height == target_width :
return numpy_image
elif target_width > image_height or target_height > image_width :
interpolation = cv2 . INTER_LANCZOS4
else :
interpolation = cv2 . INTER_AREA
2019-03-27 22:01:02 +00:00
2019-05-08 21:06:42 +00:00
return cv2 . resize ( numpy_image , ( target_width , target_height ) , interpolation = interpolation )
2019-03-27 22:01:02 +00:00
2021-12-01 22:12:16 +00:00
def RotateEXIFPILImage ( pil_image : PILImage . Image ) :
if pil_image . format == ' JPEG ' and hasattr ( pil_image , ' _getexif ' ) :
try :
exif_dict = pil_image . _getexif ( )
except :
exif_dict = None
if exif_dict is not None :
EXIF_ORIENTATION = 274
if EXIF_ORIENTATION in exif_dict :
orientation = exif_dict [ EXIF_ORIENTATION ]
if orientation == 1 :
pass # normal
elif orientation == 2 :
# mirrored horizontal
pil_image = pil_image . transpose ( PILImage . FLIP_LEFT_RIGHT )
elif orientation == 3 :
# 180
pil_image = pil_image . transpose ( PILImage . ROTATE_180 )
elif orientation == 4 :
# mirrored vertical
pil_image = pil_image . transpose ( PILImage . FLIP_TOP_BOTTOM )
elif orientation == 5 :
# seems like these 90 degree rotations are wrong, but fliping them works for my posh example images, so I guess the PIL constants are odd
# mirrored horizontal, then 90 CCW
pil_image = pil_image . transpose ( PILImage . FLIP_LEFT_RIGHT ) . transpose ( PILImage . ROTATE_90 )
elif orientation == 6 :
# 90 CW
pil_image = pil_image . transpose ( PILImage . ROTATE_270 )
elif orientation == 7 :
# mirrored horizontal, then 90 CCW
pil_image = pil_image . transpose ( PILImage . FLIP_LEFT_RIGHT ) . transpose ( PILImage . ROTATE_270 )
elif orientation == 8 :
# 90 CCW
pil_image = pil_image . transpose ( PILImage . ROTATE_90 )
return pil_image