mirror of https://github.com/aseprite/aseprite.git
Merge 57f32e2dd0
into 41e5097c33
This commit is contained in:
commit
038af64753
|
@ -97,6 +97,13 @@ add_subdirectory(app)
|
||||||
|
|
||||||
set(DATA_OUTPUT_DIR ${CMAKE_BINARY_DIR}/bin/data)
|
set(DATA_OUTPUT_DIR ${CMAKE_BINARY_DIR}/bin/data)
|
||||||
|
|
||||||
|
if(ENABLE_ASEPRITE_EXE)
|
||||||
|
set(main_target aseprite)
|
||||||
|
if(APPLE)
|
||||||
|
set(DATA_OUTPUT_DIR ${CMAKE_BINARY_DIR}/bin/${main_target}.app/Contents/Resources/data)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Clone "strings" repo with translations into bin/data/strings.git
|
# Clone "strings" repo with translations into bin/data/strings.git
|
||||||
|
|
||||||
|
@ -176,15 +183,20 @@ add_custom_target(copy_data DEPENDS clone_strings ${out_data_files})
|
||||||
# Aseprite application
|
# Aseprite application
|
||||||
|
|
||||||
if(ENABLE_ASEPRITE_EXE)
|
if(ENABLE_ASEPRITE_EXE)
|
||||||
set(main_target aseprite)
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(main_resources
|
set(main_resources
|
||||||
main/win/resources_win32.rc
|
main/win/resources_win32.rc
|
||||||
main/win/settings.manifest)
|
main/win/settings.manifest)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(${main_target}
|
set(BUNDLE_PARAM "")
|
||||||
|
set(INSTALL_OPT RUNTIME)
|
||||||
|
if(APPLE)
|
||||||
|
set(BUNDLE_PARAM MACOSX_BUNDLE)
|
||||||
|
set(INSTALL_OPT BUNDLE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(${main_target} ${BUNDLE_PARAM}
|
||||||
main/main.cpp
|
main/main.cpp
|
||||||
${main_resources})
|
${main_resources})
|
||||||
if(LAF_BACKEND STREQUAL "skia")
|
if(LAF_BACKEND STREQUAL "skia")
|
||||||
|
@ -193,6 +205,10 @@ if(ENABLE_ASEPRITE_EXE)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
set_target_properties(${main_target} PROPERTIES LINK_FLAGS "${LAF_BACKEND_LINK_FLAGS}")
|
set_target_properties(${main_target} PROPERTIES LINK_FLAGS "${LAF_BACKEND_LINK_FLAGS}")
|
||||||
|
if(APPLE)
|
||||||
|
set_target_properties(${main_target} PROPERTIES
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/main/osx/Info.plist")
|
||||||
|
endif()
|
||||||
target_link_libraries(${main_target} app-lib)
|
target_link_libraries(${main_target} app-lib)
|
||||||
add_dependencies(${main_target} copy_data)
|
add_dependencies(${main_target} copy_data)
|
||||||
|
|
||||||
|
@ -202,7 +218,7 @@ if(ENABLE_ASEPRITE_EXE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(TARGETS ${main_target}
|
install(TARGETS ${main_target}
|
||||||
RUNTIME DESTINATION bin)
|
${INSTALL_OPT} DESTINATION bin)
|
||||||
|
|
||||||
install(DIRECTORY ../data
|
install(DIRECTORY ../data
|
||||||
DESTINATION share/aseprite)
|
DESTINATION share/aseprite)
|
||||||
|
|
|
@ -1,37 +1,19 @@
|
||||||
# Desktop Integration
|
# Desktop Integration
|
||||||
# Copyright (c) 2022 Igara Studio S.A.
|
# Copyright (c) 2022-2025 Igara Studio S.A.
|
||||||
|
|
||||||
find_library(QUARTZ_LIBRARY Quartz)
|
find_library(QUICKLOOK_LIBRARY QuickLookThumbnailing)
|
||||||
|
|
||||||
add_library(AsepriteThumbnailer SHARED
|
set(extension_target AsepriteThumbnailer)
|
||||||
main.mm
|
|
||||||
|
add_executable(${extension_target} MACOSX_BUNDLE
|
||||||
thumbnail.mm)
|
thumbnail.mm)
|
||||||
|
|
||||||
target_link_libraries(AsepriteThumbnailer
|
target_link_libraries(${extension_target}
|
||||||
laf-base
|
|
||||||
dio-lib
|
dio-lib
|
||||||
render-lib
|
render-lib
|
||||||
${QUARTZ_LIBRARY})
|
${QUICKLOOK_LIBRARY})
|
||||||
|
|
||||||
set_target_properties(AsepriteThumbnailer PROPERTIES
|
set_target_properties(${extension_target} PROPERTIES
|
||||||
FRAMEWORK TRUE
|
BUNDLE_EXTENSION appex
|
||||||
MACOSX_FRAMEWORK_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist")
|
LINK_FLAGS "-Wl,-e,_NSExtensionMain"
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/Info.plist")
|
||||||
add_custom_command(
|
|
||||||
OUTPUT ${CMAKE_BINARY_DIR}/lib/AsepriteThumbnailer.qlgenerator
|
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory lib/AsepriteThumbnailer.qlgenerator
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory lib/AsepriteThumbnailer.qlgenerator/Contents
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory lib/AsepriteThumbnailer.qlgenerator/Contents/MacOS
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
|
||||||
lib/AsepriteThumbnailer.framework/Versions/A/AsepriteThumbnailer
|
|
||||||
lib/AsepriteThumbnailer.qlgenerator/Contents/MacOS
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
|
||||||
lib/AsepriteThumbnailer.framework/Versions/A/Resources/Info.plist
|
|
||||||
lib/AsepriteThumbnailer.qlgenerator/Contents
|
|
||||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/lib/AsepriteThumbnailer.qlgenerator/Contents/MacOS/AsepriteThumbnailer
|
|
||||||
${CMAKE_BINARY_DIR}/lib/AsepriteThumbnailer.qlgenerator/Contents/Info.plist
|
|
||||||
DEPENDS AsepriteThumbnailer)
|
|
||||||
|
|
||||||
add_custom_target(AsepriteThumbnailer.qlgenerator
|
|
||||||
DEPENDS ${CMAKE_BINARY_DIR}/lib/AsepriteThumbnailer.qlgenerator)
|
|
||||||
|
|
|
@ -2,57 +2,36 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDocumentTypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleTypeRole</key>
|
|
||||||
<string>QLGenerator</string>
|
|
||||||
<key>LSItemContentTypes</key>
|
|
||||||
<array>
|
|
||||||
<string>dyn.ah62d4rv4ge80c65f</string>
|
|
||||||
<string>dyn.ah62d4rv4ge80c65fsb3gw7df</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>AsepriteThumbnailer</string>
|
<string>AsepriteThumbnailer</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>org.aseprite.AsepriteThumbnailer</string>
|
<string>org.aseprite.Aseprite.AsepriteThumbnailer</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>AsepriteThumbnailer</string>
|
<string>AsepriteThumbnailer</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XPC!</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFPlugInFactories</key>
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>XPC!</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>A5E9417E-6E7A-4B2D-85A4-84E114D7A960</key>
|
<key>NSExtensionAttributes</key>
|
||||||
<string>QuickLookGeneratorPluginFactory</string>
|
|
||||||
</dict>
|
|
||||||
<key>CFPlugInTypes</key>
|
|
||||||
<dict>
|
<dict>
|
||||||
<key>5E2D9680-5022-40FA-B806-43349622E5B9</key>
|
<key>QLSupportedContentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>A5E9417E-6E7A-4B2D-85A4-84E114D7A960</string>
|
<string>org.aseprite.Aseprite.sprite</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>QLThumbnailMinimumDimension</key>
|
||||||
|
<integer>32</integer>
|
||||||
|
</dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.quicklook.thumbnail</string>
|
||||||
|
<key>NSExtensionPrincipalClass</key>
|
||||||
|
<string>ThumbnailProvider</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>CFPlugInUnloadFunction</key>
|
|
||||||
<string></string>
|
|
||||||
<key>QLThumbnailMinimumSize</key>
|
|
||||||
<real>32</real>
|
|
||||||
<key>QLPreviewWidth</key>
|
|
||||||
<real>256</real>
|
|
||||||
<key>QLPreviewHeight</key>
|
|
||||||
<real>256</real>
|
|
||||||
<key>QLNeedsToBeRunInMainThread</key>
|
|
||||||
<true/>
|
|
||||||
<key>QLSupportsConcurrentRequests</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © Igara Studio S.A. All rights reserved.</string>
|
<string>Copyright © 2001-2025, Igara Studio S.A.
|
||||||
|
All rights reserved.</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -1,234 +0,0 @@
|
||||||
// Desktop Integration
|
|
||||||
// Copyright (c) 2022 Igara Studio S.A.
|
|
||||||
//
|
|
||||||
// This file is released under the terms of the MIT license.
|
|
||||||
// Read LICENSE.txt for more information.
|
|
||||||
|
|
||||||
#include <CoreFoundation/CFPlugInCOM.h>
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
|
||||||
#include <CoreServices/CoreServices.h>
|
|
||||||
#include <QuickLook/QuickLook.h>
|
|
||||||
|
|
||||||
#include "base/debug.h"
|
|
||||||
#include "thumbnail.h"
|
|
||||||
|
|
||||||
// Just as a side note: We're using the same UUID as the Windows
|
|
||||||
// Aseprite thumbnailer.
|
|
||||||
//
|
|
||||||
// If you're going to use this code, remember to change this UUID and
|
|
||||||
// change it in the Info.plist file.
|
|
||||||
#define PLUGIN_ID "A5E9417E-6E7A-4B2D-85A4-84E114D7A960"
|
|
||||||
|
|
||||||
static HRESULT Plugin_QueryInterface(void*, REFIID, LPVOID*);
|
|
||||||
static ULONG Plugin_AddRef(void*);
|
|
||||||
static ULONG Plugin_Release(void*);
|
|
||||||
static OSStatus Plugin_GenerateThumbnailForURL(void*,
|
|
||||||
QLThumbnailRequestRef,
|
|
||||||
CFURLRef,
|
|
||||||
CFStringRef,
|
|
||||||
CFDictionaryRef,
|
|
||||||
CGSize);
|
|
||||||
static void Plugin_CancelThumbnailGeneration(void*, QLThumbnailRequestRef);
|
|
||||||
static OSStatus Plugin_GeneratePreviewForURL(void*,
|
|
||||||
QLPreviewRequestRef,
|
|
||||||
CFURLRef,
|
|
||||||
CFStringRef,
|
|
||||||
CFDictionaryRef);
|
|
||||||
static void Plugin_CancelPreviewGeneration(void*, QLPreviewRequestRef);
|
|
||||||
|
|
||||||
static QLGeneratorInterfaceStruct Plugin_vtbl = { // kQLGeneratorTypeID interface
|
|
||||||
// IUnknown
|
|
||||||
nullptr, // void* reserved
|
|
||||||
Plugin_QueryInterface,
|
|
||||||
Plugin_AddRef,
|
|
||||||
Plugin_Release,
|
|
||||||
// QLGeneratorInterface
|
|
||||||
Plugin_GenerateThumbnailForURL,
|
|
||||||
Plugin_CancelThumbnailGeneration,
|
|
||||||
Plugin_GeneratePreviewForURL,
|
|
||||||
Plugin_CancelPreviewGeneration
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO it would be nice to create a C++ smart pointer/wrapper for CFUUIDRef type
|
|
||||||
|
|
||||||
struct Plugin {
|
|
||||||
QLGeneratorInterfaceStruct* interface; // Must be a pointer
|
|
||||||
CFUUIDRef factoryID;
|
|
||||||
ULONG refCount = 1; // Starts with one reference when it's created
|
|
||||||
|
|
||||||
Plugin(CFUUIDRef factoryID)
|
|
||||||
: interface(new QLGeneratorInterfaceStruct(Plugin_vtbl))
|
|
||||||
, factoryID(factoryID)
|
|
||||||
{
|
|
||||||
CFPlugInAddInstanceForFactory(factoryID);
|
|
||||||
}
|
|
||||||
|
|
||||||
~Plugin()
|
|
||||||
{
|
|
||||||
delete interface;
|
|
||||||
if (factoryID) {
|
|
||||||
CFPlugInRemoveInstanceForFactory(factoryID);
|
|
||||||
CFRelease(factoryID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IUnknown impl
|
|
||||||
|
|
||||||
HRESULT QueryInterface(REFIID iid, LPVOID* ppv)
|
|
||||||
{
|
|
||||||
CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, iid);
|
|
||||||
|
|
||||||
if (CFEqual(interfaceID, kQLGeneratorCallbacksInterfaceID)) {
|
|
||||||
*ppv = this;
|
|
||||||
AddRef();
|
|
||||||
CFRelease(interfaceID);
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
*ppv = nullptr;
|
|
||||||
CFRelease(interfaceID);
|
|
||||||
return E_NOINTERFACE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ULONG AddRef() { return ++refCount; }
|
|
||||||
|
|
||||||
ULONG Release()
|
|
||||||
{
|
|
||||||
if (refCount == 1) {
|
|
||||||
delete this;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ASSERT(refCount != 0);
|
|
||||||
return --refCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// QLGeneratorInterfaceStruct impl
|
|
||||||
|
|
||||||
static OSStatus GenerateThumbnailForURL(QLThumbnailRequestRef thumbnail,
|
|
||||||
CFURLRef url,
|
|
||||||
CFStringRef contentTypeUTI,
|
|
||||||
CFDictionaryRef options,
|
|
||||||
CGSize maxSize)
|
|
||||||
{
|
|
||||||
CGImageRef image = desktop::get_thumbnail(url, options, maxSize);
|
|
||||||
if (!image)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
QLThumbnailRequestSetImage(thumbnail, image, nullptr);
|
|
||||||
CGImageRelease(image);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void CancelThumbnailGeneration(QLThumbnailRequestRef thumbnail)
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus GeneratePreviewForURL(QLPreviewRequestRef preview,
|
|
||||||
CFURLRef url,
|
|
||||||
CFStringRef contentTypeUTI,
|
|
||||||
CFDictionaryRef options)
|
|
||||||
{
|
|
||||||
CGImageRef image = desktop::get_thumbnail(url, options, CGSizeMake(0, 0));
|
|
||||||
if (!image)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
int w = CGImageGetWidth(image);
|
|
||||||
int h = CGImageGetHeight(image);
|
|
||||||
int wh = std::min(w, h);
|
|
||||||
if (wh < 128) {
|
|
||||||
w = 128 * w / wh;
|
|
||||||
h = 128 * h / wh;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGContextRef cg = QLPreviewRequestCreateContext(preview, CGSizeMake(w, h), YES, options);
|
|
||||||
CGContextSetInterpolationQuality(cg, kCGInterpolationNone);
|
|
||||||
CGContextDrawImage(cg, CGRectMake(0, 0, w, h), image);
|
|
||||||
QLPreviewRequestFlushContext(preview, cg);
|
|
||||||
CGImageRelease(image);
|
|
||||||
CGContextRelease(cg);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CancelPreviewGeneration(QLPreviewRequestRef preview)
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static HRESULT Plugin_QueryInterface(void* p, REFIID iid, LPVOID* ppv)
|
|
||||||
{
|
|
||||||
ASSERT(p);
|
|
||||||
return reinterpret_cast<Plugin*>(p)->QueryInterface(iid, ppv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ULONG Plugin_AddRef(void* p)
|
|
||||||
{
|
|
||||||
ASSERT(p);
|
|
||||||
return reinterpret_cast<Plugin*>(p)->AddRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
static ULONG Plugin_Release(void* p)
|
|
||||||
{
|
|
||||||
ASSERT(p);
|
|
||||||
return reinterpret_cast<Plugin*>(p)->Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus Plugin_GenerateThumbnailForURL(void* p,
|
|
||||||
QLThumbnailRequestRef thumbnail,
|
|
||||||
CFURLRef url,
|
|
||||||
CFStringRef contentTypeUTI,
|
|
||||||
CFDictionaryRef options,
|
|
||||||
CGSize maxSize)
|
|
||||||
{
|
|
||||||
ASSERT(p);
|
|
||||||
return reinterpret_cast<Plugin*>(p)->GenerateThumbnailForURL(thumbnail,
|
|
||||||
url,
|
|
||||||
contentTypeUTI,
|
|
||||||
options,
|
|
||||||
maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Plugin_CancelThumbnailGeneration(void* p, QLThumbnailRequestRef thumbnail)
|
|
||||||
{
|
|
||||||
ASSERT(p);
|
|
||||||
reinterpret_cast<Plugin*>(p)->CancelThumbnailGeneration(thumbnail);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus Plugin_GeneratePreviewForURL(void* p,
|
|
||||||
QLPreviewRequestRef preview,
|
|
||||||
CFURLRef url,
|
|
||||||
CFStringRef contentTypeUTI,
|
|
||||||
CFDictionaryRef options)
|
|
||||||
{
|
|
||||||
ASSERT(p);
|
|
||||||
return reinterpret_cast<Plugin*>(p)->GeneratePreviewForURL(preview, url, contentTypeUTI, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Plugin_CancelPreviewGeneration(void* p, QLPreviewRequestRef preview)
|
|
||||||
{
|
|
||||||
ASSERT(p);
|
|
||||||
reinterpret_cast<Plugin*>(p)->CancelPreviewGeneration(preview);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the only public entry point of the framework/plugin (the
|
|
||||||
// "QuickLookGeneratorPluginFactory" name is specified in the
|
|
||||||
// Info.list file): the factory of objects. Similar than the Win32 COM
|
|
||||||
// IClassFactory::CreateInstance()
|
|
||||||
//
|
|
||||||
// This function is used to create an instance of an object of
|
|
||||||
// kQLGeneratorTypeID type, which should implement the
|
|
||||||
// QLGeneratorInterfaceStruct interface.
|
|
||||||
extern "C" void* QuickLookGeneratorPluginFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
|
|
||||||
{
|
|
||||||
if (CFEqual(typeID, kQLGeneratorTypeID)) {
|
|
||||||
CFUUIDRef uuid = CFUUIDCreateFromString(kCFAllocatorDefault, CFSTR(PLUGIN_ID));
|
|
||||||
auto plugin = new Plugin(uuid);
|
|
||||||
CFRelease(uuid);
|
|
||||||
return plugin;
|
|
||||||
}
|
|
||||||
return nullptr; // Unknown typeID
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
// Desktop Integration
|
|
||||||
// Copyright (c) 2022 Igara Studio S.A.
|
|
||||||
//
|
|
||||||
// This file is released under the terms of the MIT license.
|
|
||||||
// Read LICENSE.txt for more information.
|
|
||||||
|
|
||||||
#ifndef DESKTOP_THUMBNAIL_H_INCLUDED
|
|
||||||
#define DESKTOP_THUMBNAIL_H_INCLUDED
|
|
||||||
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
|
||||||
#include <CoreGraphics/CoreGraphics.h>
|
|
||||||
|
|
||||||
namespace desktop {
|
|
||||||
|
|
||||||
CGImageRef get_thumbnail(CFURLRef url, CFDictionaryRef options, CGSize maxSize);
|
|
||||||
|
|
||||||
} // namespace desktop
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,11 +1,10 @@
|
||||||
// Desktop Integration
|
// Desktop Integration
|
||||||
// Copyright (c) 2022 Igara Studio S.A.
|
// Copyright (c) 2022-2025 Igara Studio S.A.
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
// Read LICENSE.txt for more information.
|
// Read LICENSE.txt for more information.
|
||||||
|
|
||||||
#include "thumbnail.h"
|
#include "app/file_system.h"
|
||||||
|
|
||||||
#include "dio/decode_delegate.h"
|
#include "dio/decode_delegate.h"
|
||||||
#include "dio/decode_file.h"
|
#include "dio/decode_file.h"
|
||||||
#include "dio/file_interface.h"
|
#include "dio/file_interface.h"
|
||||||
|
@ -13,8 +12,7 @@
|
||||||
#include "render/render.h"
|
#include "render/render.h"
|
||||||
|
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
|
#include <QuickLookThumbnailing/QuickLookThumbnailing.h>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace desktop {
|
namespace desktop {
|
||||||
|
|
||||||
|
@ -87,7 +85,7 @@ public:
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
CGImageRef get_thumbnail(CFURLRef url, CFDictionaryRef options, CGSize maxSize)
|
CGImageRef get_thumbnail(CFURLRef url, CGSize maxSize)
|
||||||
{
|
{
|
||||||
auto data = [[NSData alloc] initWithContentsOfURL:(NSURL*)url];
|
auto data = [[NSData alloc] initWithContentsOfURL:(NSURL*)url];
|
||||||
if (!data)
|
if (!data)
|
||||||
|
@ -126,6 +124,18 @@ CGImageRef get_thumbnail(CFURLRef url, CFDictionaryRef options, CGSize maxSize)
|
||||||
0,
|
0,
|
||||||
gfx::ClipF(0, 0, 0, 0, image->width(), image->height()));
|
gfx::ClipF(0, 0, 0, 0, image->width(), image->height()));
|
||||||
|
|
||||||
|
// Alpha premultiplication.
|
||||||
|
// CGBitmapContextCreate doesn't support unpremultiplied alpha images (kCGImageAlphaFirst).
|
||||||
|
doc::LockImageBits<doc::RgbTraits> bits(image.get());
|
||||||
|
doc::LockImageBits<doc::RgbTraits>::iterator it, end;
|
||||||
|
for (it = bits.begin(), end = bits.end(); it != end; ++it) {
|
||||||
|
const int a = doc::rgba_geta(*it);
|
||||||
|
*it = doc::rgba(doc::rgba_getr(*it) * a / 255,
|
||||||
|
doc::rgba_getg(*it) * a / 255,
|
||||||
|
doc::rgba_getb(*it) * a / 255,
|
||||||
|
a);
|
||||||
|
}
|
||||||
|
|
||||||
w = image->width();
|
w = image->width();
|
||||||
h = image->height();
|
h = image->height();
|
||||||
}
|
}
|
||||||
|
@ -134,9 +144,6 @@ CGImageRef get_thumbnail(CFURLRef url, CFDictionaryRef options, CGSize maxSize)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Premultiply alpha because CGBitmapContextCreate doesn't
|
|
||||||
// support unpremultiplied alpha (kCGImageAlphaFirst).
|
|
||||||
|
|
||||||
CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
||||||
CGContextRef gc = CGBitmapContextCreate(image->getPixelAddress(0, 0),
|
CGContextRef gc = CGBitmapContextCreate(image->getPixelAddress(0, 0),
|
||||||
w,
|
w,
|
||||||
|
@ -153,3 +160,42 @@ CGImageRef get_thumbnail(CFURLRef url, CFDictionaryRef options, CGSize maxSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace desktop
|
} // namespace desktop
|
||||||
|
|
||||||
|
@interface ThumbnailProvider : QLThumbnailProvider
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ThumbnailProvider
|
||||||
|
|
||||||
|
- (void)provideThumbnailForFileRequest:(QLFileThumbnailRequest*)request
|
||||||
|
completionHandler:
|
||||||
|
(void (^)(QLThumbnailReply* _Nullable, NSError* _Nullable))handler
|
||||||
|
{
|
||||||
|
CFURLRef url = (__bridge CFURLRef)(request.fileURL);
|
||||||
|
CGSize maxSize = request.maximumSize;
|
||||||
|
CGImageRef cgImage = desktop::get_thumbnail(url, maxSize);
|
||||||
|
if (!cgImage) {
|
||||||
|
handler(nil, nil);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CGSize imageSize = CGSizeMake(CGImageGetWidth(cgImage), CGImageGetHeight(cgImage));
|
||||||
|
handler([QLThumbnailReply replyWithContextSize:maxSize
|
||||||
|
currentContextDrawingBlock:^BOOL {
|
||||||
|
CGContextRef ctx = [[NSGraphicsContext currentContext] CGContext];
|
||||||
|
CGContextSaveGState(ctx);
|
||||||
|
CGFloat scale = MIN(maxSize.width / imageSize.width,
|
||||||
|
maxSize.height / imageSize.height);
|
||||||
|
CGSize scaledSize = CGSizeMake(imageSize.width * scale,
|
||||||
|
imageSize.height * scale);
|
||||||
|
CGRect drawRect = CGRectMake((maxSize.width - scaledSize.width) / 2,
|
||||||
|
(maxSize.height - scaledSize.height) / 2,
|
||||||
|
scaledSize.width,
|
||||||
|
scaledSize.height);
|
||||||
|
CGContextDrawImage(ctx, drawRect, cgImage);
|
||||||
|
CGContextRestoreGState(ctx);
|
||||||
|
CGImageRelease(cgImage);
|
||||||
|
return YES;
|
||||||
|
}],
|
||||||
|
nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
Loading…
Reference in New Issue