Render API: Add the ability to resize and encode into different formats (#1578)

* Render API: Add the ability to resize and encode into different formats

* Update API docs with new render options

* Add checks that render width and height are > 0
This commit is contained in:
Paul Friederichsen 2024-08-10 15:48:19 -05:00 committed by GitHub
parent 3e03ffb3f9
commit a78e8e8e0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 120 additions and 64 deletions

View File

@ -2069,9 +2069,18 @@ Arguments :
* `file_id`: (selective, numerical file id for the file)
* `hash`: (selective, a hexadecimal SHA256 hash for the file)
* `download`: (optional, boolean, default `false`)
* `render_format`: (optional, integer, the filetype enum value to render the file to, default `2` for PNG)
* `render_quality`: (optional, integer, the quality or PNG compression level to use for encoding the image, default `1` for PNG and `80` for JPEG and WEBP)
* `width` and `height`: (optional but must provide both if used, integer, the width and height to scale the image to)
Only use one of file_id or hash. As with metadata fetching, you may only use the hash argument if you have access to all files. If you are tag-restricted, you will have to use a file_id in the last search you ran.
Currently the only accepted values for `render_format` are:
* `1` for JPEG (`quality` sets JPEG quality 0 to 100, always progressive 4:2:0 encoding)
* `2` for PNG (`quality` sets the compression level from 0 to 9. A higher value means a smaller size and longer compression time)
* `33` for WEBP (`quality` sets WEBP quality 1 to 100, for values over 100 lossless compression is used)
The file you request must be a still image file that Hydrus can render (this includes PSD files). This request uses the client image cache.
``` title="Example request"
@ -2082,7 +2091,7 @@ The file you request must be a still image file that Hydrus can render (this inc
```
Response:
: A PNG file of the image as would be rendered in the client. It will be converted to sRGB color if the file had a color profile but the rendered PNG will not have any color profile.
: A PNG, JPEG, or WEBP file of the image as would be rendered in the client, optionally resized as specified in the query parameters. It will be converted to sRGB color if the file had a color profile but the rendered file will not have any color profile.
By default, this will set the `Content-Disposition` header to `inline`, which causes a web browser to show the file. If you set `download=true`, it will set it to `attachment`, which triggers the browser to automatically download it (or open the 'save as' dialog) instead.

View File

@ -65,7 +65,7 @@ from hydrus.client.search import ClientSearchParseSystemPredicates
from hydrus.client.gui import ClientGUIPopupMessages
# if a variable name isn't defined here, a GET with it won't work
CLIENT_API_INT_PARAMS = { 'file_id', 'file_sort_type', 'potentials_search_type', 'pixel_duplicates', 'max_hamming_distance', 'max_num_pairs' }
CLIENT_API_INT_PARAMS = { 'file_id', 'file_sort_type', 'potentials_search_type', 'pixel_duplicates', 'max_hamming_distance', 'max_num_pairs', 'width', 'height', 'render_format', 'render_quality' }
CLIENT_API_BYTE_PARAMS = { 'hash', 'destination_page_key', 'page_key', 'service_key', 'Hydrus-Client-API-Access-Key', 'Hydrus-Client-API-Session-Key', 'file_service_key', 'deleted_file_service_key', 'tag_service_key', 'tag_service_key_1', 'tag_service_key_2', 'rating_service_key', 'job_status_key' }
CLIENT_API_STRING_PARAMS = { 'name', 'url', 'domain', 'search', 'service_name', 'reason', 'tag_display_type', 'source_hash_type', 'desired_hash_type' }
CLIENT_API_JSON_PARAMS = { 'basic_permissions', 'tags', 'tags_1', 'tags_2', 'file_ids', 'download', 'only_return_identifiers', 'only_return_basic_information', 'include_blurhash', 'create_new_file_ids', 'detailed_url_information', 'hide_service_keys_tags', 'simple', 'file_sort_asc', 'return_hashes', 'return_file_ids', 'include_notes', 'include_milliseconds', 'include_services_object', 'notes', 'note_names', 'doublecheck_file_system', 'only_in_view' }
@ -2889,6 +2889,19 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl
def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
if 'render_format' in request.parsed_request_args:
format = request.parsed_request_args.GetValue( 'render_format', int )
if not format in [ HC.IMAGE_PNG, HC.IMAGE_JPEG, HC.IMAGE_WEBP ]:
raise HydrusExceptions.BadRequestException( 'Invalid render format!' )
else:
format = HC.IMAGE_PNG
try:
media_result: ClientMedia.MediaSingleton
@ -2933,16 +2946,50 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl
return
time.sleep( 0.1 )
time.sleep( 0.01 )
numpy_image = renderer.GetNumPyImage()
body = HydrusImageHandling.GeneratePNGBytesNumPy( numpy_image )
if 'width' in request.parsed_request_args and 'height' in request.parsed_request_args:
width = request.parsed_request_args.GetValue( 'width', int )
height = request.parsed_request_args.GetValue( 'height', int )
if width < 1:
raise HydrusExceptions.BadRequestException( 'Width must be greater than 0!' )
if height < 1:
raise HydrusExceptions.BadRequestException( 'Height must be greater than 0!' )
numpy_image = HydrusImageHandling.ResizeNumPyImage( numpy_image, ( width, height ) )
if 'render_quality' in request.parsed_request_args:
quality = request.parsed_request_args.GetValue( 'render_quality', int )
else:
if format == HC.IMAGE_PNG:
quality = 1 # fastest png compression
else:
quality = 80
body = HydrusImageHandling.GenerateFileBytesForRenderAPI( numpy_image, format, quality )
is_attachment = request.parsed_request_args.GetValue( 'download', bool, default_value = False )
response_context = HydrusServerResources.ResponseContext( 200, mime = HC.IMAGE_PNG, body = body, is_attachment = is_attachment, max_age = 86400 * 365 )
response_context = HydrusServerResources.ResponseContext( 200, mime = format, body = body, is_attachment = is_attachment, max_age = 86400 * 365 )
return response_context

View File

@ -353,6 +353,62 @@ def GeneratePILImageFromNumPyImage( numpy_image: numpy.array ) -> PILImage.Image
return pil_image
def GenerateFileBytesNumPy( numpy_image, ext: str = '.png', params: list[int] = [] ) -> bytes:
if len( numpy_image.shape ) == 2:
convert = cv2.COLOR_GRAY2RGB
else:
( im_height, im_width, depth ) = numpy_image.shape
if depth == 4:
convert = cv2.COLOR_RGBA2BGRA
else:
convert = cv2.COLOR_RGB2BGR
numpy_image = cv2.cvtColor( numpy_image, convert )
( result_success, result_byte_array ) = cv2.imencode( ext, numpy_image, params )
if result_success:
return result_byte_array.tostring()
else:
raise HydrusExceptions.CantRenderWithCVException( 'Image failed to encode!' )
def GenerateFileBytesForRenderAPI( numpy_image, format: int, quality: int ):
ext = HC.mime_ext_lookup[format]
params = []
if format == HC.IMAGE_PNG:
params = [ cv2.IMWRITE_PNG_COMPRESSION, quality ]
elif format == HC.IMAGE_JPEG:
params = [ cv2.IMWRITE_JPEG_QUALITY, quality, cv2.IMWRITE_JPEG_PROGRESSIVE, 1 ]
elif format == HC.IMAGE_WEBP:
params = [ cv2.IMWRITE_WEBP_QUALITY, quality ]
return GenerateFileBytesNumPy( numpy_image, ext, params )
def GenerateThumbnailNumPyFromStaticImagePath( path, target_resolution, mime ):
numpy_image = GenerateNumPyImage( path, mime )
@ -368,27 +424,10 @@ def GenerateThumbnailBytesFromNumPy( numpy_image ) -> bytes:
depth = 3
convert = cv2.COLOR_GRAY2RGB
else:
( im_height, im_width, depth ) = numpy_image.shape
numpy_image = HydrusImageNormalisation.StripOutAnyUselessAlphaChannel( numpy_image )
if depth == 4:
convert = cv2.COLOR_RGBA2BGRA
else:
convert = cv2.COLOR_RGB2BGR
numpy_image = cv2.cvtColor( numpy_image, convert )
( im_height, im_width, depth ) = numpy_image.shape
if depth == 4:
@ -403,18 +442,7 @@ def GenerateThumbnailBytesFromNumPy( numpy_image ) -> bytes:
params = CV_JPEG_THUMBNAIL_ENCODE_PARAMS
( 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!' )
return GenerateFileBytesNumPy(numpy_image, ext, params)
def GenerateThumbnailBytesFromPIL( pil_image: PILImage.Image ) -> bytes:
@ -439,34 +467,6 @@ def GenerateThumbnailBytesFromPIL( pil_image: PILImage.Image ) -> bytes:
return thumbnail_bytes
def GeneratePNGBytesNumPy( numpy_image ) -> bytes:
( im_height, im_width, depth ) = numpy_image.shape
ext = '.png'
if depth == 4:
convert = cv2.COLOR_RGBA2BGRA
else:
convert = cv2.COLOR_RGB2BGR
numpy_image = cv2.cvtColor( numpy_image, convert )
( result_success, result_byte_array ) = cv2.imencode( ext, numpy_image )
if result_success:
return result_byte_array.tostring()
else:
raise HydrusExceptions.CantRenderWithCVException( 'Image failed to encode!' )
def GetImagePixelHash( path, mime ) -> bytes: