macOS/QuickLook: support rich thumbnail #107072

Open
Ankit Meel wants to merge 10 commits from ankitm/blender:ankitm/2ql into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
2 changed files with 103 additions and 12 deletions
Showing only changes of commit aafc8e6be0 - Show all commits

View File

@ -57,12 +57,12 @@ if(UNIX AND NOT APPLE)
endif()
if(APPLE)
set(SRC
set(SRC_APPEX
src/ThumbnailProvider.mm
src/ThumbnailProvider.h
)
add_executable(blender-thumbnailer MACOSX_BUNDLE ${SRC})
add_executable(blender-thumbnailer MACOSX_BUNDLE ${SRC} ${SRC_APPEX})
setup_platform_linker_flags(blender-thumbnailer)
target_link_libraries(blender-thumbnailer
bf_blenlib

View File

@ -1,6 +1,15 @@
ankitm marked this conversation as resolved Outdated

snake_case for filenames

snake_case for filenames
#include "ThumbnailProvider.h"
#include <AppKit/NSImage.h>
#include <CoreGraphics/CGDataProvider.h>
#include <CoreGraphics/CoreGraphics.h>
#include <Foundation/Foundation.h>
#include "BLI_fileops.h"
#include "BLI_filereader.h"
#include "blendthumb.hh"
ankitm marked this conversation as resolved Outdated

Add newline before this.

Add newline before this.
/**
* This section intends to list the important steps for creating a thumbnail extension.
* qlgenerator has been deprecated and removed in platforms we support. App extensions are the way
@ -38,17 +47,88 @@
*
* # Troubleshooting
* - Is it registered with lsregister and there isn't a conflict with another plugin taking
* precendence?
* - Is it codesigned and sandboxed?
* precendence? `lsregister -dump | grep Blender.app/`
* - Is it codesigned and sandboxed? `codesign -d --entitlements - /path/to/plugin`
* - Sometimes blender-thumbnailer running in background can be killed.
* - qlmanage -r && killall Finder
* - lsregister -dump | grep Blender.app/
*
* # Triggering a thumbnail
* - qlmanage -t /path/to/file.blend
* - qlmanage -t -s 512 -o /tmp/ /path/to/file.blend
*/
class FileReaderRAII {
int src_file_ = -1;
public:
explicit FileReaderRAII(int src_file) : src_file_(src_file) {}
~FileReaderRAII()
{
if (src_file_ != -1) {
close(src_file_);
}
}
bool good()
{
return src_file_ != -1;
}
int get()
{
return src_file_;
}
};
static NSError *createErrorFromStr(NSString *errorStr)
ankitm marked this conversation as resolved Outdated

snake_case for function names

`snake_case` for function names

Java habit from work ..

Java habit from work ..
{
NSLog(@"Blender Thumbnailer Error: %@", errorStr);
return [NSError errorWithDomain:@"com.blenderfoundation.blender.thumbnailer"
code:-1
userInfo:@{NSLocalizedDescriptionKey : errorStr}];
}
static NSImage *generateNSImageForFile(const char *src_blend_path, NSError **error)
ankitm marked this conversation as resolved Outdated

snake_case for function names

`snake_case` for function names
{
ankitm marked this conversation as resolved Outdated

Remove empty line

Remove empty line
/* Open source file `src_blend`. */
FileReaderRAII src_file_fd = FileReaderRAII(BLI_open(src_blend_path, O_BINARY | O_RDONLY, 0));
ankitm marked this conversation as resolved Outdated

This name is confusing to me, it's not RAII for the FileReader type below. Would also be better to put BLI_open inside the class constructor I think.

This name is confusing to me, it's not RAII for the `FileReader` type below. Would also be better to put `BLI_open` inside the class constructor I think.
if (!src_file_fd.good()) {
*error = createErrorFromStr(@"Failed to open blend");
return nil;
}
FileReader *file_content = BLI_filereader_new_file(src_file_fd.get());
if (file_content == nullptr) {
*error = createErrorFromStr(@"Failed to read from blend");
return nil;
}
/* Extract thumbnail from file. */
Thumbnail thumb;
eThumbStatus err = blendthumb_create_thumb_from_file(file_content, &thumb);
if (err != BT_OK) {
*error = createErrorFromStr(@"Failed to create thumbnail from file");
return nil;
}
std::optional<blender::Vector<uint8_t>> png_buf_opt = blendthumb_create_png_data_from_thumb(
&thumb);
if (!png_buf_opt) {
*error = createErrorFromStr(@"Failed to create png data from thumbnail");
return nil;
}
NSData *data = [NSData dataWithBytes:png_buf_opt->data() length:png_buf_opt->size()];
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data);
CGImageRef image = CGImageCreateWithPNGDataProvider(
provider, nullptr, true, kCGRenderingIntentDefault);
CGDataProviderRelease(provider);
NSImage *uiimage = [[NSImage alloc] initWithCGImage:image size:NSZeroSize];
ankitm marked this conversation as resolved Outdated

Not sure why this is called uiimage, maybe copied from example code?

Not sure why this is called `uiimage`, maybe copied from example code?
CGImageRelease(image);
CGDataProviderRelease(provider);
ankitm marked this conversation as resolved Outdated

Don't call CGDataProviderRelease twice

Don't call `CGDataProviderRelease` twice
return uiimage;
}
@implementation ThumbnailProvider
- (void)provideThumbnailForFileRequest:(QLFileThumbnailRequest *)request
@ -56,13 +136,24 @@
NSError *_Nullable error))handler
{
NSLog(@"hello world from blender");
NSURL *foo = [[NSURL alloc] initFileURLWithFileSystemRepresentation:""
isDirectory:NO
relativeToURL:nil];
QLThumbnailReply *reply = [QLThumbnailReply replyWithImageFileURL:foo];
handler(reply, nil);
NSLog(@"Generating thumbnail for %@", request.fileURL.path);
@autoreleasepool {
NSError *error = nil;
NSImage *image = generateNSImageForFile(request.fileURL.path.UTF8String, &error);
if (image == nil) {
handler(nil, error);
return;
}
handler([QLThumbnailReply replyWithContextSize:request.maximumSize
currentContextDrawingBlock:^BOOL {
[image drawInRect:NSMakeRect(0,
0,
request.maximumSize.width,
request.maximumSize.height)];
return YES;
}],
nil);
}
}
@end