From 5192c3bfffda0eaad42f601d81cc8c82fae5ad2b Mon Sep 17 00:00:00 2001 From: Augustin Cavalier Date: Mon, 25 Apr 2022 23:10:03 -0400 Subject: [PATCH] Tracker: Move thumbnail-manipulation code to its own file. Preparatory work for refactoring thumbnail generation to not use so many resources (threads, ports, etc.) --- src/kits/tracker/IconCache.cpp | 12 +- src/kits/tracker/Jamfile | 1 + src/kits/tracker/Model.cpp | 1 + src/kits/tracker/Thumbnails.cpp | 431 ++++++++++++++++++++++++++++++++ src/kits/tracker/Thumbnails.h | 33 +++ src/kits/tracker/Utilities.cpp | 406 +----------------------------- src/kits/tracker/Utilities.h | 5 +- 7 files changed, 474 insertions(+), 415 deletions(-) create mode 100644 src/kits/tracker/Thumbnails.cpp create mode 100644 src/kits/tracker/Thumbnails.h diff --git a/src/kits/tracker/IconCache.cpp b/src/kits/tracker/IconCache.cpp index e49c4feba2..0206c46652 100644 --- a/src/kits/tracker/IconCache.cpp +++ b/src/kits/tracker/IconCache.cpp @@ -74,6 +74,7 @@ All rights reserved. #include "IconCache.h" #include "MimeTypes.h" #include "Model.h" +#include "Thumbnails.h" //#if DEBUG @@ -785,8 +786,11 @@ IconCache::GetNodeIcon(ModelNodeLazyOpener* modelOpener, if (model->IsExecutable() && (file = dynamic_cast(model->Node())) != NULL) { result = GetAppIconFromAttr(file, lazyBitmap->Get(), size); - } else - result = GetFileIconFromAttr(model, lazyBitmap->Get(), size); + } else { + result = GetThumbnailFromAttr(model, lazyBitmap->Get(), size); + if (result != B_OK && result != B_BUSY) + result = GetFileIconFromAttr(model->Node(), lazyBitmap->Get(), size); + } if (result == B_OK) { // node has its own icon, use it @@ -801,10 +805,6 @@ IconCache::GetNodeIcon(ModelNodeLazyOpener* modelOpener, entry->SetIcon(lazyBitmap->Adopt(), mode, size); } source = kNode; - } else if (result == B_BUSY) { - // still waiting for thumbnail icon to be generated, - // provide a hint to come back here for it - source = kNode; } } diff --git a/src/kits/tracker/Jamfile b/src/kits/tracker/Jamfile index dab80c771e..2d12b4f059 100644 --- a/src/kits/tracker/Jamfile +++ b/src/kits/tracker/Jamfile @@ -80,6 +80,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { TemplatesMenu.cpp Tests.cpp TextWidget.cpp + Thumbnails.cpp TitleView.cpp Tracker.cpp TrackerInitialState.cpp diff --git a/src/kits/tracker/Model.cpp b/src/kits/tracker/Model.cpp index fe88e60563..e0e6570535 100644 --- a/src/kits/tracker/Model.cpp +++ b/src/kits/tracker/Model.cpp @@ -68,6 +68,7 @@ All rights reserved. #include "FindPanel.h" #include "FSUtils.h" #include "MimeTypes.h" +#include "Thumbnails.h" #include "Tracker.h" #include "Utilities.h" diff --git a/src/kits/tracker/Thumbnails.cpp b/src/kits/tracker/Thumbnails.cpp new file mode 100644 index 0000000000..18f63fde7a --- /dev/null +++ b/src/kits/tracker/Thumbnails.cpp @@ -0,0 +1,431 @@ +/* + * Copyright 2021-2022, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Augustin Cavalier + * John Scipione + */ +#include "Thumbnails.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "Commands.h" +#include "FSUtils.h" +#include "TrackerSettings.h" + + +#ifdef B_XXL_ICON +# undef B_XXL_ICON +#endif +#define B_XXL_ICON 128 + + +namespace BPrivate { + + +status_t +GetThumbnailFromAttr(Model* model, BBitmap* icon, icon_size which) +{ + if (model == NULL || icon == NULL) + return B_BAD_VALUE; + + status_t result = model->InitCheck(); + if (result != B_OK) + return result; + + result = icon->InitCheck(); + if (result != B_OK) + return result; + + BNode* node = model->Node(); + if (node == NULL) + return B_BAD_VALUE; + + // look for a thumbnail in an attribute + time_t modtime; + bigtime_t created; + if (node->GetModificationTime(&modtime) == B_OK + && node->ReadAttr(kAttrThumbnailCreationTime, B_TIME_TYPE, 0, + &created, sizeof(bigtime_t)) == sizeof(bigtime_t)) { + if (created > (bigtime_t)modtime) { + // file has not changed, try to return an existing thumbnail + attr_info attrInfo; + if (node->GetAttrInfo(kAttrThumbnail, &attrInfo) == B_OK) { + uint8 webpData[attrInfo.size]; + if (node->ReadAttr(kAttrThumbnail, attrInfo.type, 0, + webpData, attrInfo.size) == attrInfo.size) { + BMemoryIO stream((const void*)webpData, attrInfo.size); + BBitmap thumb(BTranslationUtils::GetBitmap(&stream)); + + // convert thumb to icon size + if (which == B_XXL_ICON) { + // import icon data from attribute without resizing + result = icon->ImportBits(&thumb); + } else { + // down-scale thumb to icon size + // TODO don't make a copy, allow icon to accept views + BBitmap tmp = BBitmap(icon->Bounds(), + icon->ColorSpace(), true); + BView view(tmp.Bounds(), "", B_FOLLOW_NONE, + B_WILL_DRAW); + tmp.AddChild(&view); + if (view.LockLooper()) { + // fill with transparent + view.SetLowColor(B_TRANSPARENT_COLOR); + view.FillRect(view.Bounds(), B_SOLID_LOW); + // draw bitmap + view.SetDrawingMode(B_OP_ALPHA); + view.SetBlendingMode(B_PIXEL_ALPHA, + B_ALPHA_COMPOSITE); + view.DrawBitmap(&thumb, thumb.Bounds(), + tmp.Bounds(), B_FILTER_BITMAP_BILINEAR); + view.Sync(); + view.UnlockLooper(); + } + tmp.RemoveChild(&view); + + // copy tmp bitmap into icon + result = icon->ImportBits(&tmp); + } + // we found a thumbnail + if (result == B_OK) + return result; + } + } + // else we did not find a thumbnail + } else { + // file changed, remove all thumb attrs + char attrName[B_ATTR_NAME_LENGTH]; + while (node->GetNextAttrName(attrName) == B_OK) { + if (BString(attrName).StartsWith(kAttrThumbnail)) + node->RemoveAttr(attrName); + } + } + } + + if (ShouldGenerateThumbnail(model->MimeType())) { + // try to fetch a new thumbnail icon + result = GetThumbnailIcon(model, icon, which); + if (result == B_OK) { + // icon ready + return B_OK; + } else if (result == B_BUSY) { + // working on icon, come back later + return B_BUSY; + } + } + + return ENOENT; +} + +// #pragma mark - image thumbnails + + +struct ThumbGenParams { + ThumbGenParams(Model* _model, BFile* _file, icon_size _which, + color_space _colorSpace, port_id _port); + virtual ~ThumbGenParams(); + + status_t InitCheck() { return fInitStatus; }; + + Model* model; + BFile* file; + icon_size which; + color_space colorSpace; + port_id port; + +private: + status_t fInitStatus; +}; + + +ThumbGenParams::ThumbGenParams(Model* _model, BFile* _file, icon_size _which, + color_space _colorSpace, port_id _port) +{ + model = new(std::nothrow) Model(*_model); + file = new(std::nothrow) BFile(*_file); + which = _which; + colorSpace = _colorSpace; + port = _port; + + fInitStatus = (model == NULL || file == NULL ? B_NO_MEMORY : B_OK); +} + + +ThumbGenParams::~ThumbGenParams() +{ + delete file; + delete model; +} + + +status_t get_thumbnail(void* castToParams); +static const int32 kMsgIconData = 'ICON'; + + +BRect +ThumbBounds(BBitmap* icon, float aspectRatio) +{ + BRect thumbBounds; + + if (aspectRatio > 1) { + // wide + thumbBounds = BRect(0, 0, icon->Bounds().IntegerWidth() - 1, + floorf((icon->Bounds().IntegerHeight() - 1) / aspectRatio)); + thumbBounds.OffsetBySelf(0, floorf((icon->Bounds().IntegerHeight() + - thumbBounds.IntegerHeight()) / 2.0f)); + } else if (aspectRatio < 1) { + // tall + thumbBounds = BRect(0, 0, floorf((icon->Bounds().IntegerWidth() - 1) + * aspectRatio), icon->Bounds().IntegerHeight() - 1); + thumbBounds.OffsetBySelf(floorf((icon->Bounds().IntegerWidth() + - thumbBounds.IntegerWidth()) / 2.0f), 0); + } else { + // square + thumbBounds = icon->Bounds(); + } + + return thumbBounds; +} + + +status_t +GetThumbnailIcon(Model* model, BBitmap* icon, icon_size which) +{ + status_t result = B_ERROR; + + // create a name for the node icon generator thread (32 chars max) + icon_size w = (icon_size)B_XXL_ICON; + dev_t d = model->NodeRef()->device; + ino_t n = model->NodeRef()->node; + BString genThreadName = BString("_thumbgen_w") + << w << "_d" << d << "_n" << n << "_"; + + bool volumeReadOnly = true; + BVolume volume(model->NodeRef()->device); + if (volume.InitCheck() == B_OK) + volumeReadOnly = volume.IsReadOnly() || !volume.KnowsAttr(); + + port_id port = B_NAME_NOT_FOUND; + if (volumeReadOnly) { + // look for a port with some icon data + port = find_port(genThreadName.String()); + // give the port the same name as the generator thread + if (port != B_NAME_NOT_FOUND && port_count(port) > 0) { + // a generator thread has written some data to the port, fetch it + uint8 iconData[icon->BitsLength()]; + int32 msgCode; + int32 bytesRead = read_port(port, &msgCode, iconData, + icon->BitsLength()); + if (bytesRead == icon->BitsLength() && msgCode == kMsgIconData + && iconData != NULL) { + // fill icon data into the passed in icon + result = icon->ImportBits(iconData, icon->BitsLength(), + icon->BytesPerRow(), 0, icon->ColorSpace()); + } + + if (result == B_OK) { + // make a new port next time + delete_port(port); + port = B_NAME_NOT_FOUND; + } + } + } + + // we found an icon from a generator thread + if (result == B_OK) + return B_OK; + + // look for an existing generator thread before spawning a new one + if (find_thread(genThreadName.String()) == B_NAME_NOT_FOUND) { + // no generater thread found, spawn one + BFile* file = dynamic_cast(model->Node()); + if (file == NULL) + result = B_NOT_SUPPORTED; // node must be a file + else { + // create a new port if one doesn't already exist + if (volumeReadOnly && port == B_NAME_NOT_FOUND) + port = create_port(1, genThreadName.String()); + + ThumbGenParams* params = new ThumbGenParams(model, file, which, + icon->ColorSpace(), port); + if (params->InitCheck() == B_OK) { + // generator thread will delete params, it makes copies + resume_thread(spawn_thread(get_thumbnail, + genThreadName.String(), B_LOW_PRIORITY, params)); + result = B_BUSY; // try again later + } else + delete params; + } + } + + return result; +} + + +bool +ShouldGenerateThumbnail(const char* type) +{ + // check generate thumbnail setting, + // mime type must be an image (for now) + return TrackerSettings().GenerateImageThumbnails() + && type != NULL && BString(type).IStartsWith("image"); +} + + +// #pragma mark - thumbnail generator thread + + +status_t +get_thumbnail(void* castToParams) +{ + ThumbGenParams* params = (ThumbGenParams*)castToParams; + Model* model = params->model; + BFile* file = params->file; + icon_size which = params->which; + color_space colorSpace = params->colorSpace; + port_id port = params->port; + + // get the mime type from the model + const char* type = model->MimeType(); + + // check if attributes can be written to + bool volumeReadOnly = true; + BVolume volume(model->NodeRef()->device); + if (volume.InitCheck() == B_OK) + volumeReadOnly = volume.IsReadOnly() || !volume.KnowsAttr(); + + // see if we have a thumbnail attribute + attr_info attrInfo; + status_t result = file->GetAttrInfo(kAttrThumbnail, &attrInfo); + if (result != B_OK) { + // create a new thumbnail + + // check to see if we have a translator that works + BBitmapStream imageStream; + BBitmap* image; + if (BTranslatorRoster::Default()->Translate(file, NULL, NULL, + &imageStream, B_TRANSLATOR_BITMAP, 0, type) == B_OK + && imageStream.DetachBitmap(&image) == B_OK) { + // we have translated the image file into a BBitmap + + // check if we can write attrs + if (!volumeReadOnly) { + // write image width to an attribute + int32 width = image->Bounds().IntegerWidth(); + file->WriteAttr("Media:Width", B_INT32_TYPE, 0, &width, + sizeof(int32)); + + // write image height to an attribute + int32 height = image->Bounds().IntegerHeight(); + file->WriteAttr("Media:Height", B_INT32_TYPE, 0, &height, + sizeof(int32)); + + // convert image into a 128x128 WebP image and stash it + BBitmap thumb = BBitmap(BRect(0, 0, B_XXL_ICON - 1, + B_XXL_ICON - 1), colorSpace, true); + BView view(thumb.Bounds(), "", B_FOLLOW_NONE, + B_WILL_DRAW); + thumb.AddChild(&view); + if (view.LockLooper()) { + // fill with transparent + view.SetLowColor(B_TRANSPARENT_COLOR); + view.FillRect(view.Bounds(), B_SOLID_LOW); + // draw bitmap + view.SetDrawingMode(B_OP_ALPHA); + view.SetBlendingMode(B_PIXEL_ALPHA, + B_ALPHA_COMPOSITE); + view.DrawBitmap(image, image->Bounds(), + ThumbBounds(&thumb, image->Bounds().Width() + / image->Bounds().Height()), + B_FILTER_BITMAP_BILINEAR); + view.Sync(); + view.UnlockLooper(); + } + thumb.RemoveChild(&view); + + BBitmap* thumbPointer = &thumb; + BBitmapStream thumbStream(thumbPointer); + BMallocIO stream; + if (BTranslatorRoster::Default()->Translate(&thumbStream, + NULL, NULL, &stream, B_WEBP_FORMAT) == B_OK + && thumbStream.DetachBitmap(&thumbPointer) == B_OK) { + // write WebP image data into an attribute + file->WriteAttr(kAttrThumbnail, B_RAW_TYPE, 0, + stream.Buffer(), stream.BufferLength()); + + // write thumbnail creation time into an attribute + bigtime_t created = system_time(); + file->WriteAttr(kAttrThumbnailCreationTime, B_TIME_TYPE, + 0, &created, sizeof(bigtime_t)); + + // we wrote thumbnail to an attribute + result = B_OK; + } + } else if (port != B_NAME_NOT_FOUND) { + // create a thumb at the requested icon size + BBitmap thumb = BBitmap(BRect(0, 0, which - 1, which - 1), + colorSpace, true); + // copy image into a view bitmap, scaled and centered + BView view(thumb.Bounds(), "", B_FOLLOW_NONE, B_WILL_DRAW); + thumb.AddChild(&view); + if (view.LockLooper()) { + // fill with transparent + view.SetLowColor(B_TRANSPARENT_COLOR); + view.FillRect(view.Bounds(), B_SOLID_LOW); + // draw bitmap + view.SetDrawingMode(B_OP_ALPHA); + view.SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); + view.DrawBitmap(image, image->Bounds(), + ThumbBounds(&thumb, image->Bounds().Width() + / image->Bounds().Height()), + B_FILTER_BITMAP_BILINEAR); + view.Sync(); + view.UnlockLooper(); + } + thumb.RemoveChild(&view); + + // send icon back to the calling thread through the port + result = write_port(port, kMsgIconData, (void*)thumb.Bits(), + thumb.BitsLength()); + } + } + + delete image; + } + + if (result == B_OK) { + // trigger an icon refresh + if (!volumeReadOnly) + model->Mimeset(true); // only works on read-write volumes + else { + // send Tracker a message to tell it to update the thumbnail + BMessage message(kUpdateThumbnail); + if (message.AddInt32("device", model->NodeRef()->device) == B_OK + && message.AddUInt64("node", model->NodeRef()->node) == B_OK) { + be_app->PostMessage(&message); + } + } + } + + delete params; + + return result; +} + + +} // namespace BPrivate diff --git a/src/kits/tracker/Thumbnails.h b/src/kits/tracker/Thumbnails.h new file mode 100644 index 0000000000..10ac7d87dc --- /dev/null +++ b/src/kits/tracker/Thumbnails.h @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2022, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Augustin Cavalier + * John Scipione + */ +#ifndef _TRACKER_THUMBNAILS_H +#define _TRACKER_THUMBNAILS_H + + +#include +#include + + +namespace BPrivate { + + +class Model; + + +status_t GetThumbnailFromAttr(Model* model, BBitmap* icon, icon_size which); +status_t GetThumbnailIcon(Model* model, BBitmap* icon, icon_size which); +bool ShouldGenerateThumbnail(const char* type); + + +} // namespace BPrivate + +using namespace BPrivate; + + +#endif // _TRACKER_THUMBNAILS_H diff --git a/src/kits/tracker/Utilities.cpp b/src/kits/tracker/Utilities.cpp index 9b3f27cc0f..2a0f397319 100644 --- a/src/kits/tracker/Utilities.cpp +++ b/src/kits/tracker/Utilities.cpp @@ -49,37 +49,22 @@ All rights reserved. #include #include #include -#include -#include -#include #include #include #include #include #include -#include -#include -#include -#include -#include #include #include #include #include "Attributes.h" -#include "Commands.h" #include "ContainerWindow.h" -#include "FSUtils.h" #include "MimeTypes.h" #include "Model.h" #include "PoseView.h" -#ifdef B_XXL_ICON -# undef B_XXL_ICON -#endif -#define B_XXL_ICON 128 - #ifndef _IMPEXP_BE # define _IMPEXP_BE #endif @@ -1500,403 +1485,14 @@ GetAppIconFromAttr(BFile* file, BBitmap* icon, icon_size which) status_t -GetFileIconFromAttr(Model* model, BBitmap* icon, icon_size which) +GetFileIconFromAttr(BNode* node, BBitmap* icon, icon_size which) { - // bad input value - if (model == NULL || icon == NULL) - return B_BAD_VALUE; - - // unitialized model - status_t result = model->InitCheck(); - if (result != B_OK) - return result; - - // unitialized icon - result = icon->InitCheck(); - if (result != B_OK) - return result; - - // node not open - BNode* node = model->Node(); - if (node == NULL) - return B_BAD_VALUE; - - // look for a thumbnail in an attribute - time_t modtime; - bigtime_t created; - if (node->GetModificationTime(&modtime) == B_OK - && node->ReadAttr(kAttrThumbnailCreationTime, B_TIME_TYPE, 0, - &created, sizeof(bigtime_t)) == sizeof(bigtime_t)) { - if (created > (bigtime_t)modtime) { - // file has not changed, try to return an existing thumbnail - attr_info attrInfo; - if (node->GetAttrInfo(kAttrThumbnail, &attrInfo) == B_OK) { - uint8 webpData[attrInfo.size]; - if (node->ReadAttr(kAttrThumbnail, attrInfo.type, 0, - webpData, attrInfo.size) == attrInfo.size) { - BMemoryIO stream((const void*)webpData, attrInfo.size); - BBitmap thumb(BTranslationUtils::GetBitmap(&stream)); - - // convert thumb to icon size - if (which == B_XXL_ICON) { - // import icon data from attribute without resizing - result = icon->ImportBits(&thumb); - } else { - // down-scale thumb to icon size - // TODO don't make a copy, allow icon to accept views - BBitmap tmp = BBitmap(icon->Bounds(), - icon->ColorSpace(), true); - BView view(tmp.Bounds(), "", B_FOLLOW_NONE, - B_WILL_DRAW); - tmp.AddChild(&view); - if (view.LockLooper()) { - // fill with transparent - view.SetLowColor(B_TRANSPARENT_COLOR); - view.FillRect(view.Bounds(), B_SOLID_LOW); - // draw bitmap - view.SetDrawingMode(B_OP_ALPHA); - view.SetBlendingMode(B_PIXEL_ALPHA, - B_ALPHA_COMPOSITE); - view.DrawBitmap(&thumb, thumb.Bounds(), - tmp.Bounds(), B_FILTER_BITMAP_BILINEAR); - view.Sync(); - view.UnlockLooper(); - } - tmp.RemoveChild(&view); - - // copy tmp bitmap into icon - result = icon->ImportBits(&tmp); - } - // we found a thumbnail - if (result == B_OK) - return result; - } - } - // else we did not find a thumbnail - } else { - // file changed, remove all thumb attrs - char attrName[B_ATTR_NAME_LENGTH]; - while (node->GetNextAttrName(attrName) == B_OK) { - if (BString(attrName).StartsWith(kAttrThumbnail)) - node->RemoveAttr(attrName); - } - } - } - - if (ShouldGenerateThumbnail(model->MimeType())) { - // try to fetch a new thumbnail icon - result = GetThumbnailIcon(model, icon, which); - if (result == B_OK) { - // icon ready - return B_OK; - } else if (result == B_BUSY) { - // working on icon, come back later - return B_BUSY; - } - } - // get icon from the node info BNodeInfo nodeInfo(node); return nodeInfo.GetIcon(icon, which); } -// #pragma mark - image thumbnails - - -struct ThumbGenParams { - ThumbGenParams(Model* _model, BFile* _file, icon_size _which, - color_space _colorSpace, port_id _port); - virtual ~ThumbGenParams(); - - status_t InitCheck() { return fInitStatus; }; - - Model* model; - BFile* file; - icon_size which; - color_space colorSpace; - port_id port; - -private: - status_t fInitStatus; -}; - - -ThumbGenParams::ThumbGenParams(Model* _model, BFile* _file, icon_size _which, - color_space _colorSpace, port_id _port) -{ - model = new(std::nothrow) Model(*_model); - file = new(std::nothrow) BFile(*_file); - which = _which; - colorSpace = _colorSpace; - port = _port; - - fInitStatus = (model == NULL || file == NULL ? B_NO_MEMORY : B_OK); -} - - -ThumbGenParams::~ThumbGenParams() -{ - delete file; - delete model; -} - - -status_t get_thumbnail(void* castToParams); -static const int32 kMsgIconData = 'ICON'; - - -BRect -ThumbBounds(BBitmap* icon, float aspectRatio) -{ - BRect thumbBounds; - - if (aspectRatio > 1) { - // wide - thumbBounds = BRect(0, 0, icon->Bounds().IntegerWidth() - 1, - floorf((icon->Bounds().IntegerHeight() - 1) / aspectRatio)); - thumbBounds.OffsetBySelf(0, floorf((icon->Bounds().IntegerHeight() - - thumbBounds.IntegerHeight()) / 2.0f)); - } else if (aspectRatio < 1) { - // tall - thumbBounds = BRect(0, 0, floorf((icon->Bounds().IntegerWidth() - 1) - * aspectRatio), icon->Bounds().IntegerHeight() - 1); - thumbBounds.OffsetBySelf(floorf((icon->Bounds().IntegerWidth() - - thumbBounds.IntegerWidth()) / 2.0f), 0); - } else { - // square - thumbBounds = icon->Bounds(); - } - - return thumbBounds; -} - - -status_t -GetThumbnailIcon(Model* model, BBitmap* icon, icon_size which) -{ - status_t result = B_ERROR; - - // create a name for the node icon generator thread (32 chars max) - icon_size w = (icon_size)B_XXL_ICON; - dev_t d = model->NodeRef()->device; - ino_t n = model->NodeRef()->node; - BString genThreadName = BString("_thumbgen_w") - << w << "_d" << d << "_n" << n << "_"; - - bool volumeReadOnly = true; - BVolume volume(model->NodeRef()->device); - if (volume.InitCheck() == B_OK) - volumeReadOnly = volume.IsReadOnly() || !volume.KnowsAttr(); - - port_id port = B_NAME_NOT_FOUND; - if (volumeReadOnly) { - // look for a port with some icon data - port = find_port(genThreadName.String()); - // give the port the same name as the generator thread - if (port != B_NAME_NOT_FOUND && port_count(port) > 0) { - // a generator thread has written some data to the port, fetch it - uint8 iconData[icon->BitsLength()]; - int32 msgCode; - int32 bytesRead = read_port(port, &msgCode, iconData, - icon->BitsLength()); - if (bytesRead == icon->BitsLength() && msgCode == kMsgIconData - && iconData != NULL) { - // fill icon data into the passed in icon - result = icon->ImportBits(iconData, icon->BitsLength(), - icon->BytesPerRow(), 0, icon->ColorSpace()); - } - - if (result == B_OK) { - // make a new port next time - delete_port(port); - port = B_NAME_NOT_FOUND; - } - } - } - - // we found an icon from a generator thread - if (result == B_OK) - return B_OK; - - // look for an existing generator thread before spawning a new one - if (find_thread(genThreadName.String()) == B_NAME_NOT_FOUND) { - // no generater thread found, spawn one - BFile* file = dynamic_cast(model->Node()); - if (file == NULL) - result = B_NOT_SUPPORTED; // node must be a file - else { - // create a new port if one doesn't already exist - if (volumeReadOnly && port == B_NAME_NOT_FOUND) - port = create_port(1, genThreadName.String()); - - ThumbGenParams* params = new ThumbGenParams(model, file, which, - icon->ColorSpace(), port); - if (params->InitCheck() == B_OK) { - // generator thread will delete params, it makes copies - resume_thread(spawn_thread(get_thumbnail, - genThreadName.String(), B_LOW_PRIORITY, params)); - result = B_BUSY; // try again later - } else - delete params; - } - } - - return result; -} - - -bool -ShouldGenerateThumbnail(const char* type) -{ - // check generate thumbnail setting, - // mime type must be an image (for now) - return TrackerSettings().GenerateImageThumbnails() - && type != NULL && BString(type).IStartsWith("image"); -} - - -// #pragma mark - thumbnail generator thread - - -status_t -get_thumbnail(void* castToParams) -{ - ThumbGenParams* params = (ThumbGenParams*)castToParams; - Model* model = params->model; - BFile* file = params->file; - icon_size which = params->which; - color_space colorSpace = params->colorSpace; - port_id port = params->port; - - // get the mime type from the model - const char* type = model->MimeType(); - - // check if attributes can be written to - bool volumeReadOnly = true; - BVolume volume(model->NodeRef()->device); - if (volume.InitCheck() == B_OK) - volumeReadOnly = volume.IsReadOnly() || !volume.KnowsAttr(); - - // see if we have a thumbnail attribute - attr_info attrInfo; - status_t result = file->GetAttrInfo(kAttrThumbnail, &attrInfo); - if (result != B_OK) { - // create a new thumbnail - - // check to see if we have a translator that works - BBitmapStream imageStream; - BBitmap* image; - if (BTranslatorRoster::Default()->Translate(file, NULL, NULL, - &imageStream, B_TRANSLATOR_BITMAP, 0, type) == B_OK - && imageStream.DetachBitmap(&image) == B_OK) { - // we have translated the image file into a BBitmap - - // check if we can write attrs - if (!volumeReadOnly) { - // write image width to an attribute - int32 width = image->Bounds().IntegerWidth(); - file->WriteAttr("Media:Width", B_INT32_TYPE, 0, &width, - sizeof(int32)); - - // write image height to an attribute - int32 height = image->Bounds().IntegerHeight(); - file->WriteAttr("Media:Height", B_INT32_TYPE, 0, &height, - sizeof(int32)); - - // convert image into a 128x128 WebP image and stash it - BBitmap thumb = BBitmap(BRect(0, 0, B_XXL_ICON - 1, - B_XXL_ICON - 1), colorSpace, true); - BView view(thumb.Bounds(), "", B_FOLLOW_NONE, - B_WILL_DRAW); - thumb.AddChild(&view); - if (view.LockLooper()) { - // fill with transparent - view.SetLowColor(B_TRANSPARENT_COLOR); - view.FillRect(view.Bounds(), B_SOLID_LOW); - // draw bitmap - view.SetDrawingMode(B_OP_ALPHA); - view.SetBlendingMode(B_PIXEL_ALPHA, - B_ALPHA_COMPOSITE); - view.DrawBitmap(image, image->Bounds(), - ThumbBounds(&thumb, image->Bounds().Width() - / image->Bounds().Height()), - B_FILTER_BITMAP_BILINEAR); - view.Sync(); - view.UnlockLooper(); - } - thumb.RemoveChild(&view); - - BBitmap* thumbPointer = &thumb; - BBitmapStream thumbStream(thumbPointer); - BMallocIO stream; - if (BTranslatorRoster::Default()->Translate(&thumbStream, - NULL, NULL, &stream, B_WEBP_FORMAT) == B_OK - && thumbStream.DetachBitmap(&thumbPointer) == B_OK) { - // write WebP image data into an attribute - file->WriteAttr(kAttrThumbnail, B_RAW_TYPE, 0, - stream.Buffer(), stream.BufferLength()); - - // write thumbnail creation time into an attribute - bigtime_t created = system_time(); - file->WriteAttr(kAttrThumbnailCreationTime, B_TIME_TYPE, - 0, &created, sizeof(bigtime_t)); - - // we wrote thumbnail to an attribute - result = B_OK; - } - } else if (port != B_NAME_NOT_FOUND) { - // create a thumb at the requested icon size - BBitmap thumb = BBitmap(BRect(0, 0, which - 1, which - 1), - colorSpace, true); - // copy image into a view bitmap, scaled and centered - BView view(thumb.Bounds(), "", B_FOLLOW_NONE, B_WILL_DRAW); - thumb.AddChild(&view); - if (view.LockLooper()) { - // fill with transparent - view.SetLowColor(B_TRANSPARENT_COLOR); - view.FillRect(view.Bounds(), B_SOLID_LOW); - // draw bitmap - view.SetDrawingMode(B_OP_ALPHA); - view.SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); - view.DrawBitmap(image, image->Bounds(), - ThumbBounds(&thumb, image->Bounds().Width() - / image->Bounds().Height()), - B_FILTER_BITMAP_BILINEAR); - view.Sync(); - view.UnlockLooper(); - } - thumb.RemoveChild(&view); - - // send icon back to the calling thread through the port - result = write_port(port, kMsgIconData, (void*)thumb.Bits(), - thumb.BitsLength()); - } - } - - delete image; - } - - if (result == B_OK) { - // trigger an icon refresh - if (!volumeReadOnly) - model->Mimeset(true); // only works on read-write volumes - else { - // send Tracker a message to tell it to update the thumbnail - BMessage message(kUpdateThumbnail); - if (message.AddInt32("device", model->NodeRef()->device) == B_OK - && message.AddUInt64("node", model->NodeRef()->node) == B_OK) { - be_app->PostMessage(&message); - } - } - } - - delete params; - - return result; -} - - // #pragma mark - PrintToStream diff --git a/src/kits/tracker/Utilities.h b/src/kits/tracker/Utilities.h index e1fab5404b..8cb19ef464 100644 --- a/src/kits/tracker/Utilities.h +++ b/src/kits/tracker/Utilities.h @@ -440,10 +440,7 @@ void _ThrowOnAssert(bool, const char*, int32); // stub calls that work around BAppFile info inefficiency status_t GetAppSignatureFromAttr(BFile*, char*); status_t GetAppIconFromAttr(BFile* file, BBitmap* icon, icon_size which); -status_t GetFileIconFromAttr(Model* model, BBitmap* icon, icon_size which); -status_t GetThumbnailIcon(Model* model, BBitmap* icon, icon_size which); - -bool ShouldGenerateThumbnail(const char* type); +status_t GetFileIconFromAttr(BNode* node, BBitmap* icon, icon_size which); // debugging void HexDump(const void* buffer, int32 length);