hydrus/include/HydrusImageHandling.py

279 lines
6.3 KiB
Python
Executable File

import cStringIO
import HydrusConstants as HC
import HydrusExceptions
import HydrusThreading
import os
from PIL import _imaging
from PIL import Image as PILImage
import shutil
import struct
import threading
import time
import traceback
import HydrusData
import HydrusGlobals as HG
import HydrusPaths
import warnings
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
warnings.simplefilter( 'ignore', PILImage.DecompressionBombWarning )
def ConvertToPngIfBmp( path ):
with open( path, 'rb' ) as f:
header = f.read( 2 )
if header == 'BM':
( os_file_handle, temp_path ) = HydrusPaths.GetTempPath()
try:
with open( path, 'rb' ) as f_source:
with open( temp_path, 'wb' ) as f_dest:
HydrusPaths.CopyFileLikeToFileLike( f_source, f_dest )
pil_image = GeneratePILImage( temp_path )
pil_image.save( path, 'PNG' )
finally:
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
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
def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ):
( im_x, im_y ) = pil_image.size
if target_x >= im_x and target_y >= im_y: return pil_image
#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 )
#
return pil_image.resize( ( target_x, target_y ), PILImage.ANTIALIAS )
def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
( im_x, im_y ) = pil_image.size
#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 )
#
if im_x > target_x or im_y > target_y:
pil_image.thumbnail( ( target_x, target_y ), PILImage.ANTIALIAS )
def GeneratePILImage( path ):
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
if pil_image is None:
raise Exception( 'The file at ' + path + ' could not be rendered!' )
return pil_image
def GeneratePILImageFromNumpyImage( numpy_image ):
( h, w, depth ) = numpy_image.shape
if depth == 3:
format = 'RGB'
elif depth == 4:
format = 'RGBA'
pil_image = PILImage.frombytes( format, ( w, h ), numpy_image.data )
return pil_image
def GetGIFFrameDurations( path ):
pil_image = GeneratePILImage( path )
frame_durations = []
i = 0
while True:
try: pil_image.seek( i )
except: break
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.
else:
duration = pil_image.info[ 'duration' ]
# 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
frame_durations.append( duration )
i += 1
return frame_durations
def GetImageProperties( path ):
( ( width, height ), num_frames ) = GetResolutionAndNumFrames( path )
if num_frames > 1:
durations = GetGIFFrameDurations( path )
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
try:
pil_image.seek( 1 )
pil_image.seek( 0 )
num_frames = 1
while True:
try:
pil_image.seek( pil_image.tell() + 1 )
num_frames += 1
except: break
except:
num_frames = 1
return ( ( x, y ), num_frames )
def GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) ):
if target_x >= im_x and target_y >= im_y:
return ( im_x, im_y )
im_x = float( im_x )
im_y = float( im_y )
target_x = float( target_x )
target_y = float( target_y )
x_ratio = im_x / target_x
y_ratio = im_y / target_y
if x_ratio > y_ratio:
target_y = im_y / x_ratio
elif y_ratio > x_ratio:
target_x = im_x / y_ratio
target_x = int( target_x )
target_y = int( target_y )
return ( target_x, target_y )
def IsDecompressionBomb( path ):
warnings.simplefilter( 'error', PILImage.DecompressionBombWarning )
try:
GeneratePILImage( path )
except PILImage.DecompressionBombWarning:
return True
finally:
warnings.simplefilter( 'ignore', PILImage.DecompressionBombWarning )
return False