saving images from image editor; auto update thumbnails in file manager

This commit is contained in:
nakst 2021-09-14 15:06:03 +01:00
parent b4dfe96d85
commit 9a7c7106f4
22 changed files with 2897 additions and 388 deletions

View File

@ -355,14 +355,19 @@ void FolderDetachInstance(Instance *instance) {
folder->referenceCount--;
if (!folder->referenceCount) {
EsAssert(!folder->attachedInstances.Length());
foldersWithNoAttachedInstances.Add(folder);
if (folder->refreshing) {
loadedFolders.FindAndDeleteSwap(folder, true);
FolderDestroy(folder);
} else {
EsAssert(!folder->attachedInstances.Length());
foldersWithNoAttachedInstances.Add(folder);
if (foldersWithNoAttachedInstances.Length() > MAXIMUM_FOLDERS_WITH_NO_ATTACHED_INSTANCES) {
Folder *leastRecentlyUsed = foldersWithNoAttachedInstances[0];
loadedFolders.FindAndDeleteSwap(leastRecentlyUsed, true);
foldersWithNoAttachedInstances.Delete(0);
FolderDestroy(leastRecentlyUsed);
if (foldersWithNoAttachedInstances.Length() > MAXIMUM_FOLDERS_WITH_NO_ATTACHED_INSTANCES) {
Folder *leastRecentlyUsed = foldersWithNoAttachedInstances[0];
loadedFolders.FindAndDeleteSwap(leastRecentlyUsed, true);
foldersWithNoAttachedInstances.Delete(0);
FolderDestroy(leastRecentlyUsed);
}
}
}
}
@ -419,6 +424,8 @@ void FolderAddEntryAndUpdateInstances(Folder *folder, const char *name, size_t n
FolderEntry *entry = FolderAddEntry(folder, name, nameBytes, information, id);
ListEntry listEntry = { .entry = entry };
ThumbnailGenerateIfNeeded(folder, entry, false, true /* modified */);
for (uintptr_t i = 0; i < folder->attachedInstances.Length(); i++) {
Instance *instance = folder->attachedInstances[i];
listEntry.selected = instance == selectItem;

View File

@ -1,12 +1,8 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/strings.cpp>
#include <shared/hash_table.cpp>
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#undef IMPLEMENTATION
// TODO Possible candidates for moving in the core API:
// - String/paths utils
@ -239,6 +235,7 @@ void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename)
FolderEntry *FolderAddEntry(Folder *folder, const char *_name, size_t nameBytes, EsDirectoryChild *information, uint64_t id = 0);
void FolderAddEntries(Folder *folder, EsDirectoryChild *buffer, size_t entryCount);
uint32_t NamespaceDefaultGetFileType(String);
void ThumbnailGenerateIfNeeded(Folder *folder, FolderEntry *entry, bool fromFolderRename, bool modified);
Array<Drive> drives;
EsMutex drivesMutex;

View File

@ -545,7 +545,7 @@ void ThumbnailResize(uint32_t *bits, uint32_t originalWidth, uint32_t originalHe
}
}
void ListItemGenerateThumbnailTask(Instance *, Task *task) {
void ThumbnailGenerateTask(Instance *, Task *task) {
EsMessageMutexAcquire();
Thumbnail *thumbnail = thumbnailCache.Get(&task->id);
bool cancelTask = !thumbnail || thumbnail->referenceCount == 0;
@ -619,7 +619,7 @@ void ListItemGenerateThumbnailTask(Instance *, Task *task) {
EsMessageMutexRelease();
}
void ListItemGenerateThumbnailTaskComplete(Instance *, Task *task) {
void ThumbnailGenerateTaskComplete(Instance *, Task *task) {
Thumbnail *thumbnail = thumbnailCache.Get(&task->id);
if (thumbnail) {
@ -637,11 +637,47 @@ void ListItemGenerateThumbnailTaskComplete(Instance *, Task *task) {
StringDestroy(&task->string);
}
Thumbnail *ListItemGetThumbnail(EsElement *element) {
Instance *instance = element->instance;
ListEntry *entry = &instance->listContents[EsListViewGetIndexFromItem(element)];
Thumbnail *thumbnail = thumbnailCache.Get(&entry->entry->id);
return thumbnail;
void ThumbnailGenerateIfNeeded(Folder *folder, FolderEntry *entry, bool fromFolderRename, bool modified) {
FileType *fileType = FolderEntryGetType(folder, entry);
if (!fileType->hasThumbnailGenerator) {
return; // The file type does not support thumbnail generation.
}
Thumbnail *thumbnail;
// TODO Remove from LRU if needed.
if (modified) {
thumbnail = thumbnailCache.Get(&entry->id);
if (!thumbnail || (!thumbnail->generatingTasksInProgress && !thumbnail->bits)) {
return; // The thumbnail is not in use.
}
} else {
thumbnail = thumbnailCache.Put(&entry->id);
if (!fromFolderRename) {
thumbnail->referenceCount++;
}
if ((thumbnail->generatingTasksInProgress && !fromFolderRename) || thumbnail->bits) {
return; // The thumbnail is already being/has already been generated.
}
}
thumbnail->generatingTasksInProgress++;
String path = StringAllocateAndFormat("%s%s", STRFMT(folder->path), STRFMT(entry->GetInternalName()));
Task task = {
.string = path,
.id = entry->id,
.callback = ThumbnailGenerateTask,
.then = ThumbnailGenerateTaskComplete,
};
NonBlockingTaskQueue(task);
}
void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename) {
@ -651,38 +687,14 @@ void ListItemCreated(EsElement *element, uintptr_t index, bool fromFolderRename)
return; // The current view does not display thumbnails.
}
ListEntry *listEntry = &instance->listContents[index];
FolderEntry *entry = listEntry->entry;
FileType *fileType = FolderEntryGetType(instance->folder, entry);
ThumbnailGenerateIfNeeded(instance->folder, instance->listContents[index].entry, fromFolderRename, false /* not modified */);
}
if (!fileType->hasThumbnailGenerator) {
return; // The file type does not support thumbnail generation.
}
Thumbnail *thumbnail = thumbnailCache.Put(&entry->id);
if (!fromFolderRename) {
thumbnail->referenceCount++;
}
// TODO Remove from LRU if needed.
if ((thumbnail->generatingTasksInProgress && !fromFolderRename) || thumbnail->bits) {
return; // The thumbnail is already being/has already been generated.
}
thumbnail->generatingTasksInProgress++;
String path = StringAllocateAndFormat("%s%s", STRFMT(instance->path), STRFMT(entry->GetInternalName()));
Task task = {
.string = path,
.id = entry->id,
.callback = ListItemGenerateThumbnailTask,
.then = ListItemGenerateThumbnailTaskComplete,
};
NonBlockingTaskQueue(task);
Thumbnail *ListItemGetThumbnail(EsElement *element) {
Instance *instance = element->instance;
ListEntry *entry = &instance->listContents[EsListViewGetIndexFromItem(element)];
Thumbnail *thumbnail = thumbnailCache.Get(&entry->entry->id);
return thumbnail;
}
int ListItemMessage(EsElement *element, EsMessage *message) {

View File

@ -1,5 +1,7 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/array.cpp>
#include <shared/strings.cpp>
// TODO Previewing font files from the database.
// TODO Installing fonts.
@ -7,11 +9,6 @@
// TODO Searching/filtering fonts.
// TODO Single instance.
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#include <shared/strings.cpp>
#define SETTINGS_FILE "|Settings:/Default.ini"
struct Instance : EsInstance {

View File

@ -15,14 +15,17 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/array.cpp>
#include <shared/strings.cpp>
#ifdef OS_ESSENCE
#define IMPLEMENTATION
#include <shared/array.cpp>
#endif
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STBI_WRITE_NO_STDIO
#define STBIW_MEMMOVE EsCRTmemmove
#define STBIW_MALLOC(sz) EsCRTmalloc(sz)
#define STBIW_REALLOC(p,newsz) EsCRTrealloc(p,newsz)
#define STBIW_FREE(p) EsCRTfree(p)
#define STBIW_ASSERT EsAssert
#include <shared/stb_image_write.h>
#define TILE_SIZE (128)
@ -65,6 +68,12 @@ struct Instance : EsInstance {
bool dragged;
};
const EsInstanceClassEditorSettings editorSettings = {
INTERFACE_STRING(ImageEditorNewFileName),
INTERFACE_STRING(ImageEditorNewDocument),
ES_ICON_IMAGE_X_GENERIC,
};
const EsStyle styleBitmapSizeTextbox = {
.inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE_COMPACT,
@ -433,7 +442,7 @@ int CanvasMessage(EsElement *element, EsMessage *message) {
EsRectangle rectangle = instance->modifiedBounds;
rectangle.l += painter->offsetX, rectangle.r += painter->offsetX;
rectangle.t += painter->offsetY, rectangle.b += painter->offsetY;
EsDrawBlock(painter, rectangle, 0xFF000000 | EsColorWellGetRGB(instance->colorWell));
EsDrawBlock(painter, EsRectangleIntersection(rectangle, area), 0xFF000000 | EsColorWellGetRGB(instance->colorWell));
}
} else if ((message->type == ES_MSG_MOUSE_LEFT_DRAG || message->type == ES_MSG_MOUSE_MOVED)
&& EsMouseIsLeftHeld() && instance->commandBrush.check == ES_CHECK_CHECKED) {
@ -691,6 +700,7 @@ void MenuImage(Instance *instance, EsElement *element, EsCommand *) {
void InstanceCreate(EsMessage *message) {
Instance *instance = EsInstanceCreate(message, INTERFACE_STRING(ImageEditorTitle));
EsElement *toolbar = EsWindowGetToolbar(instance->window);
EsInstanceSetClassEditor(instance, &editorSettings);
// Register commands.
@ -706,6 +716,7 @@ void InstanceCreate(EsMessage *message) {
EsButton *button;
EsToolbarAddFileMenu(toolbar);
button = EsButtonCreate(toolbar, ES_BUTTON_DROPDOWN, ES_STYLE_PUSH_BUTTON_TOOLBAR_BIG, INTERFACE_STRING(ImageEditorImage));
EsButtonSetIcon(button, ES_ICON_IMAGE_X_GENERIC);
button->accessKey = 'I';
@ -755,7 +766,7 @@ void InstanceCreate(EsMessage *message) {
EsPanel *section = EsPanelCreate(toolbar, ES_PANEL_HORIZONTAL);
EsTextDisplayCreate(section, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(ImageEditorPropertyColor));
instance->colorWell = EsColorWellCreate(section, ES_FLAGS_DEFAULT, 0);
instance->colorWell = EsColorWellCreate(section, ES_FLAGS_DEFAULT, 0xFFFF0000);
instance->colorWell->accessKey = 'C';
EsSpacerCreate(toolbar, ES_FLAGS_DEFAULT, 0, 5, 0);
@ -790,7 +801,22 @@ void InstanceCreate(EsMessage *message) {
ImageCopyFromPaintTarget(instance, &instance->image, painter.clip);
}
void WriteCallback(void *context, void *data, int size) {
EsBufferWrite((EsBuffer *) context, data, size);
}
void SwapRedAndBlueChannels(uint32_t *bits, size_t width, size_t height, size_t stride) {
for (uintptr_t i = 0; i < height; i++) {
for (uintptr_t j = 0; j < width; j++) {
uint32_t *pixel = &bits[i * stride / 4 + j];
*pixel = (*pixel & 0xFF00FF00) | (((*pixel >> 16) | (*pixel << 16)) & 0x00FF00FF);
}
}
}
void _start() {
_init();
while (true) {
EsMessage *message = EsMessageReceive();
@ -831,6 +857,47 @@ void _start() {
EsHeapFree(bits);
EsInstanceOpenComplete(message, true);
} else if (message->type == ES_MSG_INSTANCE_SAVE) {
Instance *instance = message->instanceSave.instance;
uintptr_t extensionOffset = message->instanceSave.nameBytes;
while (extensionOffset) {
if (message->instanceSave.name[extensionOffset - 1] == '.') {
break;
} else {
extensionOffset--;
}
}
const char *extension = extensionOffset ? message->instanceSave.name + extensionOffset : "png";
size_t extensionBytes = extensionOffset ? message->instanceSave.nameBytes - extensionOffset : 3;
uint32_t *bits;
size_t width, height, stride;
EsPaintTargetStartDirectAccess(instance->bitmap, &bits, &width, &height, &stride);
EsAssert(stride == width * 4); // TODO Other strides.
SwapRedAndBlueChannels(bits, width, height, stride); // stbi_write uses the other order. We swap back below.
uint8_t _buffer[4096];
EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) };
buffer.fileStore = message->instanceSave.file;
if (0 == EsStringCompare(extension, extensionBytes, EsLiteral("jpg"))
|| 0 == EsStringCompare(extension, extensionBytes, EsLiteral("jpeg"))) {
stbi_write_jpg_to_func(WriteCallback, &buffer, width, height, 4, bits, 90);
} else if (0 == EsStringCompare(extension, extensionBytes, EsLiteral("bmp"))) {
stbi_write_bmp_to_func(WriteCallback, &buffer, width, height, 4, bits);
} else if (0 == EsStringCompare(extension, extensionBytes, EsLiteral("tga"))) {
stbi_write_tga_to_func(WriteCallback, &buffer, width, height, 4, bits);
} else {
stbi_write_png_to_func(WriteCallback, &buffer, width, height, 4, bits, stride);
}
SwapRedAndBlueChannels(bits, width, height, stride); // Swap back.
EsBufferFlushToFileStore(&buffer);
EsPaintTargetEndDirectAccess(instance->bitmap);
EsInstanceSaveComplete(message, true);
} else if (message->type == ES_MSG_INSTANCE_DESTROY) {
Instance *instance = message->instanceDestroy.instance;
EsPaintTargetDestroy(instance->bitmap);

View File

@ -11,13 +11,9 @@
#include <shared/hash.cpp>
#include <shared/strings.cpp>
#include <shared/partitions.cpp>
#include <shared/array.cpp>
#include <ports/lzma/LzmaDec.c>
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#undef IMPLEMENTATION
#define Log(...)
// TODO Error handling.
#define exit(x) EsAssert(false)

View File

@ -1,14 +1,9 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/strings.cpp>
#include <shared/array.cpp>
#include <ports/md4c/md4c.c>
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#undef IMPLEMENTATION
// TODO Inline code background?
// TODO When resizing the window, maintain the scroll position.

View File

@ -1,10 +1,6 @@
#define ES_INSTANCE_TYPE Instance
#include <essence.h>
#include <shared/array.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#undef IMPLEMENTATION
// TODO Single instance.
// TODO Sorting lists.

View File

@ -9,9 +9,10 @@
#ifdef USE_STB_IMAGE
#define STB_IMAGE_IMPLEMENTATION
#define STBI_MALLOC(sz) EsCRTmalloc(sz)
#define STBI_REALLOC(p,newsz) EsCRTrealloc(p,newsz)
#define STBI_FREE(p) EsCRTfree(p)
#define STBI_MALLOC(sz) EsCRTmalloc(sz)
#define STBI_REALLOC(p,newsz) EsCRTrealloc(p,newsz)
#define STBI_FREE(p) EsCRTfree(p)
#define STBI_ASSERT(x) EsAssert(x)
#define STBI_NO_STDIO
#define STBI_ONLY_PNG
#define STBI_ONLY_JPEG
@ -37,10 +38,6 @@
#include <shared/strings.cpp>
#include <shared/common.cpp>
#define IMPLEMENTATION
#include <shared/array.cpp>
#undef IMPLEMENTATION
struct EnumString { const char *cName; int value; };
#include <bin/enum_strings_array.h>
@ -970,6 +967,10 @@ EsMessage *EsMessageReceive() {
m.instanceSave.file->handles = 1;
m.instanceSave.instance = InstanceFromWindowID(message.message.tabOperation.id);
APIInstance *instance = (APIInstance *) m.instanceSave.instance->_private;
m.instanceSave.name = instance->startupInformation->filePath;
m.instanceSave.nameBytes = instance->startupInformation->filePathBytes;
if (m.instanceSave.file->error == ES_SUCCESS) {
EsMemoryCopy(&message.message, &m, sizeof(EsMessage));
return &message.message;

View File

@ -2521,14 +2521,18 @@ void DesktopSyscall(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) {
char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s", document->pathBytes, document->path);
char *newPath = EsStringAllocateAndFormat(&newPathBytes, "%s/%s", folderBytes, document->path, newNameBytes, newName);
EsMessage m = {};
m.type = ES_MSG_INSTANCE_RENAME_RESPONSE;
m.tabOperation.id = instance->embeddedWindowID;
m.tabOperation.error = EsPathMove(oldPath, oldPathBytes, newPath, newPathBytes);
EsMessagePostRemote(instance->process->handle, &m);
if (oldPathBytes == newPathBytes && 0 == EsMemoryCompare(oldPath, newPath, oldPathBytes)) {
// Same name.
} else {
EsMessage m = {};
m.type = ES_MSG_INSTANCE_RENAME_RESPONSE;
m.tabOperation.id = instance->embeddedWindowID;
m.tabOperation.error = EsPathMove(oldPath, oldPathBytes, newPath, newPathBytes);
EsMessagePostRemote(instance->process->handle, &m);
if (m.tabOperation.error == ES_SUCCESS) {
InstanceAnnouncePathMoved(nullptr, oldPath, oldPathBytes, newPath, newPathBytes);
if (m.tabOperation.error == ES_SUCCESS) {
InstanceAnnouncePathMoved(nullptr, oldPath, oldPathBytes, newPath, newPathBytes);
}
}
EsHeapFree(oldPath);

View File

@ -5174,7 +5174,7 @@ int FileMenuNameTextboxMessage(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_TEXTBOX_EDIT_END) {
APIInstance *instance = (APIInstance *) element->instance->_private;
if (!message->endEdit.rejected) {
if (!message->endEdit.rejected && !message->endEdit.unchanged) {
size_t newNameBytes;
char *newName = EsTextboxGetContents(instance->fileMenuNameTextbox, &newNameBytes);
uint8_t *buffer = (uint8_t *) EsHeapAllocate(1 + newNameBytes, false);

View File

@ -1691,7 +1691,7 @@ struct EsMessageGetBreadcrumb {
};
struct EsMessageEndEdit {
bool rejected;
bool rejected, unchanged;
};
// Instance messages.
@ -1706,6 +1706,7 @@ struct EsMessageInstanceOpen {
struct EsMessageInstanceSave {
ES_INSTANCE_TYPE *instance;
EsFileStore *file;
STRING name;
};
struct EsMessageInstanceDestroy {

View File

@ -6,6 +6,7 @@
#ifdef ENABLE_POSIX_SUBSYSTEM
#define ARRAY_DEFINITIONS_ONLY
#include <shared/array.cpp>
extern "C" void *ProcessorTLSRead(uintptr_t offset);

View File

@ -3179,9 +3179,12 @@ void EsTextboxStartEdit(EsTextbox *textbox) {
void TextboxEndEdit(EsTextbox *textbox, bool reject) {
if ((textbox->flags & ES_TEXTBOX_EDIT_BASED) && textbox->editing) {
TextboxSetActiveLine(textbox, -1);
textbox->editing = false;
EsMessage m = { ES_MSG_TEXTBOX_EDIT_END };
m.endEdit.rejected = reject;
m.endEdit.unchanged = textbox->dataBytes == textbox->editStartContentBytes
&& 0 == EsMemoryCompare(textbox->data, textbox->editStartContent, textbox->dataBytes);
if (reject || ES_REJECTED == EsMessageSend(textbox, &m)) {
EsTextboxSelectAll(textbox);

View File

@ -125,6 +125,7 @@ KSpinlock ipiLock;
#include <shared/heap.cpp>
#include <shared/arena.cpp>
#else
#define ARRAY_IMPLEMENTATION_ONLY
#include <shared/array.cpp>
#include <shared/partitions.cpp>
#endif

View File

@ -448,7 +448,9 @@ void *MMMapShared(MMSpace *space, MMSharedRegion *sharedRegion, uintptr_t offset
// Panics on failure.
void MMCheckUnusable(uintptr_t physicalStart, size_t bytes);
#define ARRAY_DEFINITIONS_ONLY
#include <shared/array.cpp>
#undef ARRAY_DEFINITIONS_ONLY
typedef SimpleList MMObjectCacheItem;

View File

@ -1,4 +1,4 @@
#ifndef IMPLEMENTATION
#ifndef ARRAY_IMPLEMENTATION_ONLY
struct _ArrayHeader {
size_t length, allocated;
@ -71,7 +71,9 @@ struct Array {
}
};
#else
#endif
#ifndef ARRAY_DEFINITIONS_ONLY
bool _ArrayMaybeInitialise(void **array, size_t itemSize, EsHeap *heap) {
if (*array) return true;

File diff suppressed because it is too large Load Diff

1719
shared/stb_image_write.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -189,6 +189,9 @@ DEFINE_INTERFACE_STRING(ImageEditorPickTool, "Pick tool");
DEFINE_INTERFACE_STRING(ImageEditorUnsupportedFormat, "The image is in an unsupported format. Try opening it with another application.");
DEFINE_INTERFACE_STRING(ImageEditorNewFileName, "untitled.png");
DEFINE_INTERFACE_STRING(ImageEditorNewDocument, "New bitmap image");
DEFINE_INTERFACE_STRING(ImageEditorTitle, "Image Editor");
// Text Editor.

View File

@ -287,6 +287,7 @@ Option options[] = {
{ "Flag._ALWAYS_USE_VBE", OPTION_TYPE_BOOL, { .b = false } },
{ "Dependency.ACPICA", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.stb_image", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.stb_image_write", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.stb_sprintf", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.HarfBuzz", OPTION_TYPE_BOOL, { .b = true } },
{ "Dependency.FreeType", OPTION_TYPE_BOOL, { .b = true } },

View File

@ -1266,6 +1266,8 @@ int main(int argc, char **argv) {
strcat(kernelCompileFlags, " -DUSE_ACPICA ");
} else if (0 == strcmp(s.key, "Dependency.stb_image") && atoi(s.value)) {
strcat(commonCompileFlags, " -DUSE_STB_IMAGE ");
} else if (0 == strcmp(s.key, "Dependency.stb_image_write") && atoi(s.value)) {
strcat(commonCompileFlags, " -DUSE_STB_IMAGE_WRITE ");
} else if (0 == strcmp(s.key, "Dependency.stb_sprintf") && atoi(s.value)) {
strcat(commonCompileFlags, " -DUSE_STB_SPRINTF ");
} else if (0 == strcmp(s.key, "Dependency.HarfBuzz") && atoi(s.value)) {