hydrus/hydrus/client/ClientSerialisable.py

374 lines
10 KiB
Python

import collections
import cv2
import numpy
import os
import shutil
import struct
from qtpy import QtCore as QC
from qtpy import QtGui as QG
from qtpy import QtWidgets as QW
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientImageHandling
from hydrus.client import ClientParsing
from hydrus.client import ClientPaths
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import QtPorting as QP
from hydrus.client.importing import ClientImporting
if cv2.__version__.startswith( '2' ):
IMREAD_UNCHANGED = cv2.CV_LOAD_IMAGE_UNCHANGED
else:
IMREAD_UNCHANGED = cv2.IMREAD_UNCHANGED
def CreateTopImage( width, title, payload_description, text ):
text_extent_qt_image = HG.client_controller.bitmap_manager.GetQtImage( 20, 20, 24 )
painter = QG.QPainter( text_extent_qt_image )
text_font = QW.QApplication.font()
basic_font_size = text_font.pointSize()
payload_description_font = QW.QApplication.font()
payload_description_font.setPointSize( int( basic_font_size * 1.4 ) )
title_font = QW.QApplication.font()
title_font.setPointSize( int( basic_font_size * 2.0 ) )
texts_to_draw = []
current_y = 6
for ( t, f ) in ( ( title, title_font ), ( payload_description, payload_description_font ), ( text, text_font ) ):
painter.setFont( f )
wrapped_texts = WrapText( painter, t, width - 20 )
line_height = painter.fontMetrics().height()
wrapped_texts_with_ys = []
if len( wrapped_texts ) > 0:
current_y += 10
for wrapped_text in wrapped_texts:
wrapped_texts_with_ys.append( ( wrapped_text, current_y ) )
current_y += line_height + 4
texts_to_draw.append( ( wrapped_texts_with_ys, f ) )
current_y += 6
top_height = current_y
del painter
del text_extent_qt_image
#
top_qt_image = HG.client_controller.bitmap_manager.GetQtImage( width, top_height, 24 )
painter = QG.QPainter( top_qt_image )
painter.setBackground( QG.QBrush( QC.Qt.white ) )
painter.eraseRect( painter.viewport() )
#
painter.drawPixmap( width-16-5, 5, CC.global_pixmaps().file_repository )
#
for ( wrapped_texts_with_ys, f ) in texts_to_draw:
painter.setFont( f )
for ( wrapped_text, y ) in wrapped_texts_with_ys:
( text_size, wrapped_text ) = ClientGUIFunctions.GetTextSizeFromPainter( painter, wrapped_text )
ClientGUIFunctions.DrawText( painter, ( width - text_size.width() ) // 2, y, wrapped_text )
del painter
data_bytearray = top_qt_image.bits()
if QP.qtpy.PYSIDE2:
data_bytes = bytes( data_bytearray )
elif QP.qtpy.PYQT5:
data_bytes = data_bytearray.asstring( top_height * width * 3 )
top_image_rgb = numpy.fromstring( data_bytes, dtype = 'uint8' ).reshape( ( top_height, width, 3 ) )
top_image = cv2.cvtColor( top_image_rgb, cv2.COLOR_RGB2GRAY )
top_height_header = struct.pack( '!H', top_height )
byte0 = top_height_header[0:1]
byte1 = top_height_header[1:2]
top_image[0][0] = ord( byte0 )
top_image[0][1] = ord( byte1 )
return top_image
def DumpToPNG( width, payload_bytes, title, payload_description, text, path ):
payload_bytes_length = len( payload_bytes )
header_and_payload_bytes_length = payload_bytes_length + 4
payload_height = int( header_and_payload_bytes_length / width )
if ( header_and_payload_bytes_length / width ) % 1.0 > 0:
payload_height += 1
top_image = CreateTopImage( width, title, payload_description, text )
payload_length_header = struct.pack( '!I', payload_bytes_length )
num_empty_bytes = payload_height * width - header_and_payload_bytes_length
header_and_payload_bytes = payload_length_header + payload_bytes + b'\x00' * num_empty_bytes
payload_image = numpy.fromstring( header_and_payload_bytes, dtype = 'uint8' ).reshape( ( payload_height, width ) )
finished_image = numpy.concatenate( ( top_image, payload_image ) )
# this is to deal with unicode paths, which cv2 can't handle
( os_file_handle, temp_path ) = HydrusPaths.GetTempPath( suffix = '.png' )
try:
cv2.imwrite( temp_path, finished_image, [ cv2.IMWRITE_PNG_COMPRESSION, 9 ] )
HydrusPaths.MirrorFile( temp_path, path )
except Exception as e:
HydrusData.ShowException( e )
raise Exception( 'Could not save the png!' )
finally:
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
def GetPayloadBytes( payload_obj ):
if isinstance( payload_obj, bytes ):
return payload_obj
elif isinstance( payload_obj, str ):
return bytes( payload_obj, 'utf-8' )
else:
return payload_obj.DumpToNetworkBytes()
def GetPayloadTypeString( payload_obj ):
if isinstance( payload_obj, bytes ):
return 'Bytes'
elif isinstance( payload_obj, str ):
return 'String'
elif isinstance( payload_obj, HydrusSerialisable.SerialisableList ):
type_string_counts = collections.Counter()
for o in payload_obj:
type_string_counts[ GetPayloadTypeString( o ) ] += 1
type_string = ', '.join( ( HydrusData.ToHumanInt( count ) + ' ' + s for ( s, count ) in list(type_string_counts.items()) ) )
return 'A list of ' + type_string
elif isinstance( payload_obj, HydrusSerialisable.SerialisableBase ):
return payload_obj.SERIALISABLE_NAME
else:
return repr( type( payload_obj ) )
def GetPayloadDescriptionAndBytes( payload_obj ):
payload_bytes = GetPayloadBytes( payload_obj )
payload_description = GetPayloadTypeString( payload_obj ) + ' - ' + HydrusData.ToHumanBytes( len( payload_bytes ) )
return ( payload_description, payload_bytes )
def LoadFromPNG( path ):
# this is to deal with unicode paths, which cv2 can't handle
( os_file_handle, temp_path ) = HydrusPaths.GetTempPath()
try:
HydrusPaths.MirrorFile( path, temp_path )
numpy_image = cv2.imread( temp_path, flags = IMREAD_UNCHANGED )
except Exception as e:
HydrusData.ShowException( e )
raise Exception( 'That did not appear to be a valid image!' )
finally:
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
try:
height = numpy_image.shape[0]
width = numpy_image.shape[1]
if len( numpy_image.shape ) > 2:
depth = numpy_image.shape[2]
if depth != 1:
raise Exception( 'The file did not appear to be monochrome!' )
try:
complete_data = numpy_image.tostring()
top_height_header = complete_data[:2]
( top_height, ) = struct.unpack( '!H', top_height_header )
payload_and_header_bytes = complete_data[ width * top_height : ]
except:
raise Exception( 'Header bytes were invalid!' )
try:
payload_length_header = payload_and_header_bytes[:4]
( payload_bytes_length, ) = struct.unpack( '!I', payload_length_header )
payload_bytes = payload_and_header_bytes[ 4 : 4 + payload_bytes_length ]
except:
raise Exception( 'Payload bytes were invalid!' )
except Exception as e:
HydrusData.PrintException( e )
message = 'The image loaded, but it did not seem to be a hydrus serialised png! The error was: {}'.format( str( e ) )
message += os.linesep * 2
message += 'If you believe this is a legit non-resized, non-converted hydrus serialised png, please send it to hydrus_dev.'
raise Exception( message )
return payload_bytes
def TextExceedsWidth( painter, text, width ):
( text_size, text ) = ClientGUIFunctions.GetTextSizeFromPainter( painter, text )
return text_size.width() > width
def WrapText( painter, text, width ):
words = text.split( ' ' )
lines = []
next_line = []
for word in words:
if word == '':
continue
potential_next_line = list( next_line )
potential_next_line.append( word )
if TextExceedsWidth( painter, ' '.join( potential_next_line ), width ):
if len( potential_next_line ) == 1: # one very long word
lines.append( ' '.join( potential_next_line ) )
next_line = []
else:
lines.append( ' '.join( next_line ) )
next_line = [ word ]
else:
next_line = potential_next_line
if len( next_line ) > 0:
lines.append( ' '.join( next_line ) )
return lines