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:
parent
3e03ffb3f9
commit
a78e8e8e0b
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
Loading…
Reference in New Issue