Krita full preview support (#1454)

* Krita full preview support

Mark krita files as viewable
Use same data that's used for thumbnails to generate full preview

* Parse maindoc.xml instead of doing regex magic

This should fix getting dimensions wrong for some files

* Use direct PIL image loading for Krita thumbs

* Remove unused HydrusArchiveHandling.ReadSingleFileFromZip

* Update Krita in filetype docs

---------

Co-authored-by: Paul Friederichsen <floogulinc@gmail.com>
Co-authored-by: Hydrus Network Developer <hydrus.admin@gmail.com>
This commit is contained in:
Valerii Malov 2023-10-14 22:29:49 +03:00 committed by GitHub
parent 882eaa2147
commit 408ac2aaa5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 90 additions and 83 deletions

View File

@ -77,15 +77,15 @@ This is a list of all filetypes Hydrus can import. Hydrus determines the filetyp
## Image Project Files
| Filetype | Extension | MIME type | Thumbnails | Viewable in Hydrus | Notes |
| --------- | ------------ | ----------------------------- | :--------: | :----------------: | ----------------- |
| psd | `.psd` | `image/vnd.adobe.photoshop` | ✅ | ✅ | Adobe Photoshop |
| clip | `.clip` | `application/clip`[^1] | ✅ | ❌ | Clip Studio Paint |
| sai2 | `.sai2` | `application/sai2`[^1] | ❌ | ❌ | PaintTool SAI2 |
| krita | `.kra` | `application/x-krita` | ✅ | ❌ | Krita |
| svg | `.svg` | `image/svg+xml` | ✅ | ❌ | |
| xcf | `.xcf` | `application/x-xcf` | ❌ | ❌ | GIMP |
| procreate | `.procreate` | `application/x-procreate`[^1] | ✅ | ❌ | Procreate app |
| Filetype | Extension | MIME type | Thumbnails | Viewable in Hydrus | Notes |
| --------- | ------------ | ----------------------------- | :--------: | :----------------: | -------------------------------------------------------------------------------- |
| psd | `.psd` | `image/vnd.adobe.photoshop` | ✅ | ✅ | Adobe Photoshop. Hydrus shows the embedded preview image if present in the file. |
| clip | `.clip` | `application/clip`[^1] | ✅ | ❌ | Clip Studio Paint |
| sai2 | `.sai2` | `application/sai2`[^1] | ❌ | ❌ | PaintTool SAI2 |
| krita | `.kra` | `application/x-krita` | ✅ | ✅ | Krita. Hydrus shows the embedded preview image if present in the file. |
| svg | `.svg` | `image/svg+xml` | ✅ | ❌ | |
| xcf | `.xcf` | `application/x-xcf` | ❌ | ❌ | GIMP |
| procreate | `.procreate` | `application/x-procreate`[^1] | ✅ | ❌ | Procreate app |
## Archives

View File

@ -81,6 +81,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
media_view[ HC.APPLICATION_PSD ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, image_zoom_info )
media_view[ HC.APPLICATION_KRITA ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, image_zoom_info )
return media_view

View File

@ -11,20 +11,6 @@ def ExtractSingleFileFromZip( path_to_zip, filename_to_extract, extract_into_fil
with open( extract_into_file_path, "wb" ) as writer:
writer.write( reader.read() )
def ReadSingleFileFromZip( path_to_zip, filename_to_extract ):
with zipfile.ZipFile( path_to_zip ) as zip_handle:
with zip_handle.open( filename_to_extract ) as reader:
return reader.read()
def GetZipAsPath( path_to_zip, path_in_zip="" ):

View File

@ -890,7 +890,7 @@ ARCHIVES = [
APPLICATION_ZIP
]
VIEWABLE_IMAGE_PROJECT_FILES = { APPLICATION_PSD }
VIEWABLE_IMAGE_PROJECT_FILES = { APPLICATION_PSD, APPLICATION_KRITA }
# zip files that have a `mimetype` file inside
OPEN_DOCUMENT_ZIPS = { APPLICATION_KRITA, APPLICATION_EPUB }
@ -927,7 +927,7 @@ PIL_HEIF_MIMES = {
MIMES_THAT_DEFINITELY_HAVE_AUDIO = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) )
MIMES_THAT_MAY_HAVE_AUDIO = tuple( list( MIMES_THAT_DEFINITELY_HAVE_AUDIO ) + list( VIDEO ) )
APPLICATIONS_WITH_THUMBNAILS = set( { IMAGE_SVG, APPLICATION_PDF, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_KRITA, APPLICATION_PROCREATE } ).union( VIEWABLE_IMAGE_PROJECT_FILES )
APPLICATIONS_WITH_THUMBNAILS = set( { IMAGE_SVG, APPLICATION_PDF, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_PROCREATE } ).union( VIEWABLE_IMAGE_PROJECT_FILES )
MIMES_WITH_THUMBNAILS = set( IMAGES ).union( ANIMATIONS ).union( VIDEO ).union( APPLICATIONS_WITH_THUMBNAILS )

View File

@ -111,24 +111,21 @@ def GenerateThumbnailNumPy( path, target_resolution, mime, duration, num_frames,
elif mime == HC.APPLICATION_KRITA:
( os_file_handle, temp_path ) = HydrusTemp.GetTempPath()
try:
HydrusKritaHandling.ExtractZippedImageToPath( path, temp_path )
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
thumbnail_numpy = HydrusKritaHandling.GenerateThumbnailNumPyFromKraPath( path, target_resolution, clip_rect = clip_rect )
except Exception as e:
if not isinstance( e, HydrusExceptions.NoThumbnailFileException ):
HydrusData.Print( 'Problem generating thumbnail for "{}":'.format( path ) )
HydrusData.PrintException( e )
thumb_path = os.path.join( HC.STATIC_DIR, 'krita.png' )
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
finally:
HydrusTemp.CleanUpTempPath( os_file_handle, temp_path )
elif mime == HC.APPLICATION_PROCREATE:

View File

@ -1,72 +1,83 @@
import typing
from hydrus.core import HydrusArchiveHandling
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusTemp
from hydrus.core.images import HydrusImageHandling
from PIL import Image as PILImage
import xml.etree.ElementTree as ET
import re
KRITA_FILE_THUMB = "preview.png"
KRITA_FILE_MERGED = "mergedimage.png"
def ExtractZippedImageToPath( path_to_zip, temp_path_file ):
def MergedPILImageFromKra(path):
try:
file_obj = HydrusArchiveHandling.GetZipAsPath( path, KRITA_FILE_MERGED ).open('rb')
return HydrusImageHandling.GeneratePILImage( file_obj )
HydrusArchiveHandling.ExtractSingleFileFromZip( path_to_zip, KRITA_FILE_MERGED, temp_path_file )
except FileNotFoundError:
return
except KeyError:
pass
raise HydrusExceptions.UnsupportedFileException( f'Could not read {KRITA_FILE_MERGED} from this Krita file' )
def ThumbnailPILImageFromKra(path):
try:
file_obj = HydrusArchiveHandling.GetZipAsPath( path, KRITA_FILE_THUMB ).open('rb')
return HydrusImageHandling.GeneratePILImage( file_obj )
HydrusArchiveHandling.ExtractSingleFileFromZip( path_to_zip, KRITA_FILE_THUMB, temp_path_file )
except FileNotFoundError:
except KeyError:
raise HydrusExceptions.NoThumbnailFileException( f'Could not read {KRITA_FILE_THUMB} from this Krita file' )
def GenerateThumbnailNumPyFromKraPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
try:
pil_image = MergedPILImageFromKra( path )
except:
pil_image = ThumbnailPILImageFromKra( path )
if clip_rect is not None:
raise HydrusExceptions.NoThumbnailFileException( f'This krita file had no {KRITA_FILE_MERGED} or {KRITA_FILE_THUMB}!' )
pil_image = HydrusImageHandling.ClipPILImage( pil_image, clip_rect )
thumbnail_pil_image = pil_image.resize( target_resolution, PILImage.LANCZOS )
numpy_image = HydrusImageHandling.GenerateNumPyImageFromPILImage( thumbnail_pil_image )
return numpy_image
# TODO: animation and frame stuff which is also in the maindoc.xml
def GetKraProperties( path ):
DOCUMENT_INFO_FILE = "maindoc.xml"
# TODO: probably actually parse the xml instead of using regex
FIND_KEY_VALUE = re.compile(r"([a-z\-_]+)\s*=\s*['\"]([^'\"]+)", re.IGNORECASE)
width = None
height = None
try:
with HydrusArchiveHandling.GetZipAsPath( path, DOCUMENT_INFO_FILE ).open('r') as reader:
for line in reader:
for match in FIND_KEY_VALUE.findall( line ):
key, value = match
if key == "width" and value.isdigit():
width = int(value)
if key == "height" and value.isdigit():
height = int(value)
if width is not None and height is not None:
break
except KeyError:
try:
data_file = HydrusArchiveHandling.GetZipAsPath( path, DOCUMENT_INFO_FILE ).open('rb')
root = ET.parse(data_file)
image_tag = root.find('{http://www.calligra.org/DTD/krita}IMAGE')
width = int(image_tag.attrib['width'])
raise HydrusExceptions.NoResolutionFileException( f'This krita file had no {DOCUMENT_INFO_FILE}!' )
height = int(image_tag.attrib['height'])
return ( width, height )
return ( width, height )
except:
raise HydrusExceptions.NoResolutionFileException( f'This krita file had no {DOCUMENT_INFO_FILE} or it contains no resolution!' )

View File

@ -30,6 +30,7 @@ from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPSDHandling
from hydrus.core import HydrusKritaHandling
from hydrus.core.images import HydrusImageColours
from hydrus.core.images import HydrusImageMetadata
from hydrus.core.images import HydrusImageNormalisation
@ -144,6 +145,16 @@ def GenerateNumPyImage( path, mime, force_pil = False ) -> numpy.array:
return HydrusImageNormalisation.StripOutAnyUselessAlphaChannel( numpy_image )
if mime == HC.APPLICATION_KRITA:
if HG.media_load_report_mode:
HydrusData.ShowText( 'Loading KRA' )
pil_image = HydrusKritaHandling.MergedPILImageFromKra( path )
return GenerateNumPyImageFromPILImage( pil_image )
if mime in PIL_ONLY_MIMETYPES: