2018-09-19 21:54:51 +00:00
import collections
2016-11-09 23:13:22 +00:00
import cv2
import numpy
2016-11-16 20:21:43 +00:00
import os
2016-11-09 23:13:22 +00:00
import struct
2020-05-20 21:36:02 +00:00
2019-11-14 03:56:30 +00:00
from qtpy import QtCore as QC
from qtpy import QtGui as QG
from qtpy import QtWidgets as QW
2020-05-20 21:36:02 +00:00
2021-12-22 22:31:23 +00:00
from hydrus . core import HydrusCompression
2020-07-29 20:52:44 +00:00
from hydrus . core import HydrusData
from hydrus . core import HydrusGlobals as HG
from hydrus . core import HydrusPaths
from hydrus . core import HydrusSerialisable
2021-10-27 21:12:33 +00:00
from hydrus . core import HydrusTemp
2024-01-03 21:21:53 +00:00
from hydrus . core . files . images import HydrusImageHandling
2020-07-29 20:52:44 +00:00
2020-05-20 21:36:02 +00:00
from hydrus . client import ClientConstants as CC
2024-02-14 21:20:24 +00:00
from hydrus . client import ClientGlobals as CG
2020-12-02 22:04:38 +00:00
from hydrus . client . gui import ClientGUIFunctions
2016-11-09 23:13:22 +00:00
2021-12-22 22:31:23 +00:00
# ok, the serialised png format is:
# the png is monochrome, no alpha channel (mode 'L' in PIL)
# the data is read left to right in visual pixels. one pixel = one byte
# first two bytes (i.e. ( 0, 0 ) and ( 1, 0 )), are a big endian unsigned short (!H), and say how tall the header is in number of rows. the actual data starts after that
# first four bytes of data are a big endian unsigned int (!I) saying how long the payload is in bytes
# read that many pixels after that, you got the payload
# it should be zlib compressed these days and is most likely a dumped hydrus serialisable object, which is a json guy with a whole complicated loading system. utf-8 it into a string and you are half way there :^)
2016-11-16 20:21:43 +00:00
if cv2 . __version__ . startswith ( ' 2 ' ) :
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
IMREAD_UNCHANGED = cv2 . CV_LOAD_IMAGE_UNCHANGED
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
else :
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
IMREAD_UNCHANGED = cv2 . IMREAD_UNCHANGED
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
2016-12-14 21:19:07 +00:00
def CreateTopImage ( width , title , payload_description , text ) :
2016-11-09 23:13:22 +00:00
2024-02-14 21:20:24 +00:00
text_extent_qt_image = CG . client_controller . bitmap_manager . GetQtImage ( 20 , 20 , 24 )
2016-11-09 23:13:22 +00:00
2019-11-14 03:56:30 +00:00
painter = QG . QPainter ( text_extent_qt_image )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
text_font = QW . QApplication . font ( )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
basic_font_size = text_font . pointSize ( )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
payload_description_font = QW . QApplication . font ( )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
payload_description_font . setPointSize ( int ( basic_font_size * 1.4 ) )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
title_font = QW . QApplication . font ( )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
title_font . setPointSize ( int ( basic_font_size * 2.0 ) )
2016-11-16 20:21:43 +00:00
2019-04-24 22:18:50 +00:00
texts_to_draw = [ ]
2016-11-16 20:21:43 +00:00
2019-04-24 22:18:50 +00:00
current_y = 6
2016-11-16 20:21:43 +00:00
2019-04-24 22:18:50 +00:00
for ( t , f ) in ( ( title , title_font ) , ( payload_description , payload_description_font ) , ( text , text_font ) ) :
2016-11-09 23:13:22 +00:00
2019-11-14 03:56:30 +00:00
painter . setFont ( f )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
wrapped_texts = WrapText ( painter , t , width - 20 )
line_height = painter . fontMetrics ( ) . height ( )
2016-11-16 20:21:43 +00:00
2019-04-24 22:18:50 +00:00
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
2016-11-16 20:21:43 +00:00
2019-04-24 22:18:50 +00:00
texts_to_draw . append ( ( wrapped_texts_with_ys , f ) )
2016-11-16 20:21:43 +00:00
2019-04-24 22:18:50 +00:00
current_y + = 6
top_height = current_y
2019-11-14 03:56:30 +00:00
del painter
del text_extent_qt_image
2016-11-16 20:21:43 +00:00
#
2024-02-14 21:20:24 +00:00
top_qt_image = CG . client_controller . bitmap_manager . GetQtImage ( width , top_height , 24 )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
painter = QG . QPainter ( top_qt_image )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
painter . setBackground ( QG . QBrush ( QC . Qt . white ) )
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
painter . eraseRect ( painter . viewport ( ) )
2016-11-16 20:21:43 +00:00
#
2020-03-11 21:52:11 +00:00
painter . drawPixmap ( width - 16 - 5 , 5 , CC . global_pixmaps ( ) . file_repository )
2016-11-16 20:21:43 +00:00
#
2019-04-24 22:18:50 +00:00
for ( wrapped_texts_with_ys , f ) in texts_to_draw :
2016-11-16 20:21:43 +00:00
2019-11-14 03:56:30 +00:00
painter . setFont ( f )
2016-11-16 20:21:43 +00:00
2019-04-24 22:18:50 +00:00
for ( wrapped_text , y ) in wrapped_texts_with_ys :
2020-12-02 22:04:38 +00:00
( text_size , wrapped_text ) = ClientGUIFunctions . GetTextSizeFromPainter ( painter , wrapped_text )
2019-04-24 22:18:50 +00:00
2020-12-02 22:04:38 +00:00
ClientGUIFunctions . DrawText ( painter , ( width - text_size . width ( ) ) / / 2 , y , wrapped_text )
2019-04-24 22:18:50 +00:00
2016-11-09 23:13:22 +00:00
2019-11-14 03:56:30 +00:00
del painter
2016-11-09 23:13:22 +00:00
2022-01-26 21:57:04 +00:00
top_image_rgb = ClientGUIFunctions . ConvertQtImageToNumPy ( top_qt_image )
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
top_image = cv2 . cvtColor ( top_image_rgb , cv2 . COLOR_RGB2GRAY )
2016-11-09 23:13:22 +00:00
top_height_header = struct . pack ( ' !H ' , top_height )
2019-01-09 22:59:03 +00:00
byte0 = top_height_header [ 0 : 1 ]
byte1 = top_height_header [ 1 : 2 ]
2016-11-09 23:13:22 +00:00
top_image [ 0 ] [ 0 ] = ord ( byte0 )
top_image [ 0 ] [ 1 ] = ord ( byte1 )
2016-11-16 20:21:43 +00:00
return top_image
2020-06-24 21:25:24 +00:00
def DumpToPNG ( width , payload_bytes , title , payload_description , text , path ) :
2016-11-16 20:21:43 +00:00
2019-01-09 22:59:03 +00:00
payload_bytes_length = len ( payload_bytes )
2016-11-16 20:21:43 +00:00
2019-01-09 22:59:03 +00:00
header_and_payload_bytes_length = payload_bytes_length + 4
2016-11-16 20:21:43 +00:00
2019-01-09 22:59:03 +00:00
payload_height = int ( header_and_payload_bytes_length / width )
2016-11-16 20:21:43 +00:00
2019-01-09 22:59:03 +00:00
if ( header_and_payload_bytes_length / width ) % 1.0 > 0 :
2016-11-16 20:21:43 +00:00
payload_height + = 1
2016-12-14 21:19:07 +00:00
top_image = CreateTopImage ( width , title , payload_description , text )
2016-11-16 20:21:43 +00:00
2019-01-09 22:59:03 +00:00
payload_length_header = struct . pack ( ' !I ' , payload_bytes_length )
2016-11-09 23:13:22 +00:00
2019-01-09 22:59:03 +00:00
num_empty_bytes = payload_height * width - header_and_payload_bytes_length
2016-11-09 23:13:22 +00:00
2019-01-09 22:59:03 +00:00
header_and_payload_bytes = payload_length_header + payload_bytes + b ' \x00 ' * num_empty_bytes
2016-11-09 23:13:22 +00:00
2019-01-09 22:59:03 +00:00
payload_image = numpy . fromstring ( header_and_payload_bytes , dtype = ' uint8 ' ) . reshape ( ( payload_height , width ) )
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
finished_image = numpy . concatenate ( ( top_image , payload_image ) )
2016-11-30 20:24:17 +00:00
# this is to deal with unicode paths, which cv2 can't handle
2021-10-27 21:12:33 +00:00
( os_file_handle , temp_path ) = HydrusTemp . GetTempPath ( suffix = ' .png ' )
2016-11-30 20:24:17 +00:00
try :
cv2 . imwrite ( temp_path , finished_image , [ cv2 . IMWRITE_PNG_COMPRESSION , 9 ] )
2020-05-13 19:03:16 +00:00
HydrusPaths . MirrorFile ( temp_path , path )
2016-11-30 20:24:17 +00:00
except Exception as e :
HydrusData . ShowException ( e )
2023-11-08 21:42:59 +00:00
raise Exception ( ' Could not save the png! ' ) from e
2016-11-30 20:24:17 +00:00
finally :
2021-10-27 21:12:33 +00:00
HydrusTemp . CleanUpTempPath ( os_file_handle , temp_path )
2016-11-30 20:24:17 +00:00
2017-12-20 22:55:48 +00:00
2021-12-22 22:31:23 +00:00
def GetPayloadBytesAndLength ( payload_obj ) :
2017-12-20 22:55:48 +00:00
2019-01-09 22:59:03 +00:00
if isinstance ( payload_obj , bytes ) :
2017-12-20 22:55:48 +00:00
2021-12-22 22:31:23 +00:00
return ( HydrusCompression . CompressBytesToBytes ( payload_obj ) , len ( payload_obj ) )
2019-01-09 22:59:03 +00:00
elif isinstance ( payload_obj , str ) :
2021-12-22 22:31:23 +00:00
return ( HydrusCompression . CompressStringToBytes ( payload_obj ) , len ( payload_obj ) )
2017-12-20 22:55:48 +00:00
else :
2021-12-22 22:31:23 +00:00
payload_string = payload_obj . DumpToString ( )
return ( HydrusCompression . CompressStringToBytes ( payload_string ) , len ( payload_string ) )
2017-12-20 22:55:48 +00:00
2016-12-14 21:19:07 +00:00
def GetPayloadTypeString ( payload_obj ) :
2016-11-16 20:21:43 +00:00
2019-01-09 22:59:03 +00:00
if isinstance ( payload_obj , bytes ) :
return ' Bytes '
elif isinstance ( payload_obj , str ) :
2017-12-20 22:55:48 +00:00
return ' String '
elif isinstance ( payload_obj , HydrusSerialisable . SerialisableList ) :
2016-11-16 20:21:43 +00:00
2018-09-19 21:54:51 +00:00
type_string_counts = collections . Counter ( )
for o in payload_obj :
type_string_counts [ GetPayloadTypeString ( o ) ] + = 1
2019-01-09 22:59:03 +00:00
type_string = ' , ' . join ( ( HydrusData . ToHumanInt ( count ) + ' ' + s for ( s , count ) in list ( type_string_counts . items ( ) ) ) )
2018-09-19 21:54:51 +00:00
return ' A list of ' + type_string
2016-12-14 21:19:07 +00:00
2017-12-20 22:55:48 +00:00
elif isinstance ( payload_obj , HydrusSerialisable . SerialisableBase ) :
return payload_obj . SERIALISABLE_NAME
2016-12-14 21:19:07 +00:00
else :
2017-12-20 22:55:48 +00:00
return repr ( type ( payload_obj ) )
2016-11-16 20:21:43 +00:00
2019-01-09 22:59:03 +00:00
def GetPayloadDescriptionAndBytes ( payload_obj ) :
2016-11-16 20:21:43 +00:00
2021-12-22 22:31:23 +00:00
( payload_bytes , payload_length ) = GetPayloadBytesAndLength ( payload_obj )
2016-12-14 21:19:07 +00:00
2021-12-22 22:31:23 +00:00
payload_description = GetPayloadTypeString ( payload_obj ) + ' - ' + HydrusData . ToHumanBytes ( payload_length )
2016-12-14 21:19:07 +00:00
2019-01-09 22:59:03 +00:00
return ( payload_description , payload_bytes )
2016-11-09 23:13:22 +00:00
2022-01-26 21:57:04 +00:00
def LoadFromQtImage ( qt_image : QG . QImage ) :
numpy_image = ClientGUIFunctions . ConvertQtImageToNumPy ( qt_image )
return LoadFromNumPyImage ( numpy_image )
2023-11-08 21:42:59 +00:00
2020-06-24 21:25:24 +00:00
def LoadFromPNG ( path ) :
2016-11-09 23:13:22 +00:00
2016-11-30 20:24:17 +00:00
# this is to deal with unicode paths, which cv2 can't handle
2021-10-27 21:12:33 +00:00
( os_file_handle , temp_path ) = HydrusTemp . GetTempPath ( )
2016-11-30 20:24:17 +00:00
2016-11-16 20:21:43 +00:00
try :
2020-05-13 19:03:16 +00:00
HydrusPaths . MirrorFile ( path , temp_path )
2016-11-30 20:24:17 +00:00
2021-06-23 21:11:38 +00:00
try :
2021-12-01 22:12:16 +00:00
# unchanged because we want exact byte data, no conversions or other gubbins
2021-06-23 21:11:38 +00:00
numpy_image = cv2 . imread ( temp_path , flags = IMREAD_UNCHANGED )
2021-06-30 21:27:35 +00:00
if numpy_image is None :
raise Exception ( )
2021-06-23 21:11:38 +00:00
except Exception as e :
try :
2023-10-04 20:51:17 +00:00
# dequantize = False because we don't want to convert our greyscale bytes to RGB
2021-06-23 21:11:38 +00:00
2021-12-01 22:12:16 +00:00
pil_image = HydrusImageHandling . GeneratePILImage ( temp_path , dequantize = False )
2023-11-08 21:42:59 +00:00
# leave strip_useless_alpha = True in here just to catch the very odd LA situation
2021-12-01 22:12:16 +00:00
numpy_image = HydrusImageHandling . GenerateNumPyImageFromPILImage ( pil_image )
2021-06-23 21:11:38 +00:00
except Exception as e :
HydrusData . ShowException ( e )
2023-11-08 21:42:59 +00:00
raise Exception ( ' " {} " did not appear to be a valid image! ' . format ( path ) ) from e
2021-06-23 21:11:38 +00:00
2016-11-16 20:21:43 +00:00
2016-11-30 20:24:17 +00:00
finally :
2021-10-27 21:12:33 +00:00
HydrusTemp . CleanUpTempPath ( os_file_handle , temp_path )
2016-11-30 20:24:17 +00:00
2016-11-16 20:21:43 +00:00
2022-01-26 21:57:04 +00:00
return LoadFromNumPyImage ( numpy_image )
2023-11-08 21:42:59 +00:00
2022-01-26 21:57:04 +00:00
def LoadFromNumPyImage ( numpy_image : numpy . array ) :
2016-11-16 20:21:43 +00:00
try :
2021-05-05 20:12:11 +00:00
height = numpy_image . shape [ 0 ]
width = numpy_image . shape [ 1 ]
if len ( numpy_image . shape ) > 2 :
2019-03-13 21:04:21 +00:00
2021-05-05 20:12:11 +00:00
depth = numpy_image . shape [ 2 ]
2019-03-13 21:04:21 +00:00
2021-05-05 20:12:11 +00:00
if depth != 1 :
2022-12-07 22:41:53 +00:00
numpy_image = numpy_image [ : , : , 0 ] . copy ( ) # let's fetch one channel. if the png is a perfect RGB conversion of the original (or, let's say, a Firefox bmp export), this actually works
2021-05-05 20:12:11 +00:00
2019-03-13 21:04:21 +00:00
2016-11-16 20:21:43 +00:00
2019-03-13 21:04:21 +00:00
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! ' )
2016-11-16 20:21:43 +00:00
2019-03-13 21:04:21 +00:00
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! ' )
2016-11-16 20:21:43 +00:00
except Exception as e :
2021-05-05 20:12:11 +00:00
HydrusData . PrintException ( e )
2023-04-26 21:10:03 +00:00
message = ' The image loaded, but it did not seem to be a hydrus serialised png! The error was: {} ' . format ( repr ( e ) )
2024-04-10 20:36:05 +00:00
message + = ' \n ' * 2
2019-03-13 21:04:21 +00:00
message + = ' If you believe this is a legit non-resized, non-converted hydrus serialised png, please send it to hydrus_dev. '
2016-11-16 20:21:43 +00:00
2019-03-13 21:04:21 +00:00
raise Exception ( message )
2016-11-16 20:21:43 +00:00
2019-01-09 22:59:03 +00:00
return payload_bytes
2016-11-09 23:13:22 +00:00
2021-12-22 22:31:23 +00:00
def LoadStringFromPNG ( path : str ) - > str :
payload_bytes = LoadFromPNG ( path )
try :
payload_string = HydrusCompression . DecompressBytesToString ( payload_bytes )
except :
# older payloads were not compressed
payload_string = str ( payload_bytes , ' utf-8 ' )
return payload_string
2019-11-14 03:56:30 +00:00
def TextExceedsWidth ( painter , text , width ) :
2016-11-09 23:13:22 +00:00
2020-12-02 22:04:38 +00:00
( text_size , text ) = ClientGUIFunctions . GetTextSizeFromPainter ( painter , text )
2016-11-09 23:13:22 +00:00
2020-02-26 22:28:52 +00:00
return text_size . width ( ) > width
2016-11-09 23:13:22 +00:00
2019-11-14 03:56:30 +00:00
def WrapText ( painter , text , width ) :
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
words = text . split ( ' ' )
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
lines = [ ]
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
next_line = [ ]
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
for word in words :
if word == ' ' :
continue
potential_next_line = list ( next_line )
potential_next_line . append ( word )
2019-11-14 03:56:30 +00:00
if TextExceedsWidth ( painter , ' ' . join ( potential_next_line ) , width ) :
2016-11-16 20:21:43 +00:00
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
2016-11-09 23:13:22 +00:00
2016-11-16 20:21:43 +00:00
if len ( next_line ) > 0 :
lines . append ( ' ' . join ( next_line ) )
return lines
2016-12-14 21:19:07 +00:00