2013-02-19 00:11:43 +00:00
|
|
|
import cStringIO
|
|
|
|
import HydrusConstants as HC
|
2014-03-05 22:44:02 +00:00
|
|
|
import HydrusExceptions
|
2014-05-21 21:37:35 +00:00
|
|
|
import HydrusThreading
|
2013-08-07 22:25:18 +00:00
|
|
|
import os
|
2014-10-22 22:31:58 +00:00
|
|
|
from PIL import _imaging
|
2013-02-19 00:11:43 +00:00
|
|
|
from PIL import Image as PILImage
|
2013-08-07 22:25:18 +00:00
|
|
|
import shutil
|
2013-02-19 00:11:43 +00:00
|
|
|
import struct
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
import traceback
|
2015-03-25 22:04:19 +00:00
|
|
|
import HydrusData
|
2017-05-10 21:33:58 +00:00
|
|
|
import HydrusGlobals as HG
|
2015-11-04 22:30:28 +00:00
|
|
|
import HydrusPaths
|
2017-10-04 17:51:58 +00:00
|
|
|
import warnings
|
|
|
|
|
|
|
|
warnings.simplefilter( 'ignore', PILImage.DecompressionBombWarning )
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2013-08-07 22:25:18 +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
|
|
|
|
|
|
|
if header == 'BM':
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2015-11-04 22:30:28 +00:00
|
|
|
( os_file_handle, temp_path ) = HydrusPaths.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:
|
|
|
|
|
2015-11-04 22:30:28 +00:00
|
|
|
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
|
2015-03-25 22:04:19 +00:00
|
|
|
|
2013-02-19 00:11:43 +00:00
|
|
|
|
|
|
|
|
2016-06-29 19:55:46 +00:00
|
|
|
def Dequantize( pil_image ):
|
|
|
|
|
|
|
|
if pil_image.mode not in ( 'RGBA', 'RGB' ):
|
|
|
|
|
|
|
|
if pil_image.mode == 'LA' or ( pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ) ):
|
|
|
|
|
|
|
|
pil_image = pil_image.convert( 'RGBA' )
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
pil_image = pil_image.convert( 'RGB' )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return pil_image
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ):
|
2013-02-19 00:11:43 +00:00
|
|
|
|
|
|
|
( im_x, im_y ) = pil_image.size
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
if target_x >= im_x and target_y >= im_y: return pil_image
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
#if pil_image.mode == 'RGB': # low quality resize screws up alpha channel!
|
|
|
|
#
|
|
|
|
# if im_x > 2 * target_x and im_y > 2 * target_y: pil_image.thumbnail( ( 2 * target_x, 2 * target_y ), PILImage.NEAREST )
|
|
|
|
#
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
return pil_image.resize( ( target_x, target_y ), PILImage.ANTIALIAS )
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
( im_x, im_y ) = pil_image.size
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2014-06-11 21:20:42 +00:00
|
|
|
#if pil_image.mode == 'RGB': # low quality resize screws up alpha channel!
|
|
|
|
#
|
|
|
|
# if im_x > 2 * target_x or im_y > 2 * target_y: pil_image.thumbnail( ( 2 * target_x, 2 * target_y ), PILImage.NEAREST )
|
|
|
|
#
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2015-11-25 22:00:57 +00:00
|
|
|
if im_x > target_x or im_y > target_y:
|
|
|
|
|
|
|
|
pil_image.thumbnail( ( target_x, target_y ), PILImage.ANTIALIAS )
|
|
|
|
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2015-10-21 21:53:10 +00:00
|
|
|
def GeneratePILImage( path ):
|
|
|
|
|
2016-02-03 22:12:53 +00:00
|
|
|
fp = open( path, 'rb' )
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
pil_image = PILImage.open( fp )
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
|
|
# pil doesn't clean up its open file on exception, jej
|
|
|
|
|
|
|
|
fp.close()
|
|
|
|
|
|
|
|
raise
|
|
|
|
|
2015-10-21 21:53:10 +00:00
|
|
|
|
|
|
|
if pil_image is None:
|
|
|
|
|
|
|
|
raise Exception( 'The file at ' + path + ' could not be rendered!' )
|
|
|
|
|
|
|
|
|
|
|
|
return pil_image
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
def GeneratePILImageFromNumpyImage( numpy_image ):
|
|
|
|
|
|
|
|
( h, w, depth ) = numpy_image.shape
|
|
|
|
|
2016-10-12 21:52:50 +00:00
|
|
|
if depth == 3:
|
|
|
|
|
|
|
|
format = 'RGB'
|
|
|
|
|
|
|
|
elif depth == 4:
|
|
|
|
|
|
|
|
format = 'RGBA'
|
|
|
|
|
2014-06-25 20:37:06 +00:00
|
|
|
|
2015-10-21 21:53:10 +00:00
|
|
|
pil_image = PILImage.frombytes( format, ( w, h ), numpy_image.data )
|
2014-06-25 20:37:06 +00:00
|
|
|
|
|
|
|
return pil_image
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
def GetGIFFrameDurations( path ):
|
|
|
|
|
2015-10-21 21:53:10 +00:00
|
|
|
pil_image = GeneratePILImage( path )
|
2014-05-07 22:42:30 +00:00
|
|
|
|
|
|
|
frame_durations = []
|
|
|
|
|
|
|
|
i = 0
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
2015-10-21 21:53:10 +00:00
|
|
|
try: pil_image.seek( i )
|
2014-05-07 22:42:30 +00:00
|
|
|
except: break
|
|
|
|
|
2015-10-21 21:53:10 +00:00
|
|
|
if 'duration' not in pil_image.info:
|
|
|
|
|
|
|
|
duration = 83 # Set a 12 fps default when duration is missing or too funky to extract. most stuff looks ok at this.
|
|
|
|
|
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 ):
|
|
|
|
|
|
|
|
duration = 80
|
|
|
|
|
2014-05-07 22:42:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
frame_durations.append( duration )
|
|
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
|
|
|
|
return frame_durations
|
|
|
|
|
2014-05-14 20:46:38 +00:00
|
|
|
def GetImageProperties( path ):
|
|
|
|
|
|
|
|
( ( width, height ), num_frames ) = GetResolutionAndNumFrames( path )
|
|
|
|
|
|
|
|
if num_frames > 1:
|
|
|
|
|
2014-05-28 21:03:24 +00:00
|
|
|
durations = GetGIFFrameDurations( path )
|
2014-05-14 20:46:38 +00:00
|
|
|
|
|
|
|
duration = sum( durations )
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
duration = None
|
|
|
|
num_frames = None
|
|
|
|
|
|
|
|
|
|
|
|
return ( ( width, height ), duration, num_frames )
|
|
|
|
|
|
|
|
def GetResolutionAndNumFrames( path ):
|
|
|
|
|
|
|
|
pil_image = GeneratePILImage( path )
|
|
|
|
|
|
|
|
( x, y ) = pil_image.size
|
2013-02-19 00:11:43 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
|
2014-05-14 20:46:38 +00:00
|
|
|
pil_image.seek( 1 )
|
|
|
|
pil_image.seek( 0 )
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2014-05-14 20:46:38 +00:00
|
|
|
num_frames = 1
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2014-05-14 20:46:38 +00:00
|
|
|
while True:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
pil_image.seek( pil_image.tell() + 1 )
|
|
|
|
num_frames += 1
|
|
|
|
|
|
|
|
except: break
|
|
|
|
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2017-08-23 21:34:25 +00:00
|
|
|
except:
|
|
|
|
|
|
|
|
num_frames = 1
|
|
|
|
|
2014-05-14 20:46:38 +00:00
|
|
|
|
|
|
|
return ( ( x, y ), num_frames )
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
def GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) ):
|
2014-05-14 20:46:38 +00:00
|
|
|
|
2016-06-01 20:04:15 +00:00
|
|
|
if target_x >= im_x and target_y >= im_y:
|
|
|
|
|
|
|
|
return ( im_x, im_y )
|
|
|
|
|
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
im_x = float( im_x )
|
|
|
|
im_y = float( im_y )
|
2014-05-14 20:46:38 +00:00
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
target_x = float( target_x )
|
|
|
|
target_y = float( target_y )
|
|
|
|
|
|
|
|
x_ratio = im_x / target_x
|
|
|
|
y_ratio = im_y / target_y
|
|
|
|
|
2016-06-01 20:04:15 +00:00
|
|
|
if x_ratio > y_ratio:
|
|
|
|
|
|
|
|
target_y = im_y / x_ratio
|
|
|
|
|
|
|
|
elif y_ratio > x_ratio:
|
|
|
|
|
|
|
|
target_x = im_x / y_ratio
|
|
|
|
|
2013-02-19 00:11:43 +00:00
|
|
|
|
2016-06-01 20:04:15 +00:00
|
|
|
target_x = int( target_x )
|
|
|
|
target_y = int( target_y )
|
2014-05-14 20:46:38 +00:00
|
|
|
|
2014-05-21 21:37:35 +00:00
|
|
|
return ( target_x, target_y )
|
2017-01-25 22:56:55 +00:00
|
|
|
|
2017-10-04 17:51:58 +00:00
|
|
|
def IsDecompressionBomb( path ):
|
|
|
|
|
|
|
|
warnings.simplefilter( 'error', PILImage.DecompressionBombWarning )
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
GeneratePILImage( path )
|
|
|
|
|
|
|
|
except PILImage.DecompressionBombWarning:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
warnings.simplefilter( 'ignore', PILImage.DecompressionBombWarning )
|
|
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|