-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: storing thumbnails on disk #201
base: master
Are you sure you want to change the base?
Changes from all commits
ebea027
920d258
b6d6986
afd395c
9a023e2
e38918e
44db2d0
919a8c9
87377e9
43dcd14
d840a87
9b80899
06b19ca
99c7624
0d4e79c
2035a9e
25b7ab1
65f1ec4
74b46a1
b3a14f3
a975462
41a8a06
de506dc
37dde1a
7716523
88f2d10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Image instance: pixel data and meta info. | ||
// Copyright (C) 2021 Artem Senichev <[email protected]> | ||
// Copyright (C) 2024 Rentib <[email protected]> | ||
|
||
#include "thumbnail.h" | ||
|
||
#include "config.h" | ||
#include "image.h" | ||
#include "memdata.h" | ||
#include "pixmap.h" | ||
|
||
#include <errno.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <sys/stat.h> | ||
#include <time.h> | ||
#include <unistd.h> | ||
|
||
/** | ||
* Makes directories like `mkdir -p`. | ||
* @param path absolute path to the directory to be created, should not be null | ||
* or empty | ||
* @return true if successful | ||
*/ | ||
static bool make_directories(const char* path) | ||
{ | ||
char* path_copy = str_dup(path, NULL); // maybe use [PATH_MAX] buffer | ||
char* slash = path_copy; | ||
|
||
while (true) { | ||
slash = strchr(slash + 1, '/'); | ||
Rentib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!slash) { | ||
break; | ||
} | ||
*slash = '\0'; | ||
if (mkdir(path_copy, 0755) && errno != EEXIST) { | ||
free(path_copy); | ||
return false; | ||
} | ||
*slash = '/'; | ||
} | ||
|
||
free(path_copy); | ||
return true; | ||
} | ||
|
||
static bool get_thumb_path(char** path, const char* source) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, this is a problem. I will fix it to not pass source, but the absolute file path. |
||
{ | ||
const char* prefix = "XDG_CACHE_HOME"; | ||
Rentib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const char* postfix = "/swayimg"; | ||
const char* prefix_fallback = "HOME"; | ||
const char* postfix_fallback = "/.cache/swayimg"; | ||
if ((!(*path = expand_path(prefix, postfix)) && | ||
!(*path = expand_path(prefix_fallback, postfix_fallback))) || | ||
!(*path = str_append(source, 0, path))) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
void thumbnail_params(struct thumbnail_params* params, | ||
const struct image* image, size_t size, bool fill, | ||
bool antialias) | ||
{ | ||
const struct pixmap* full = &image->frames[0].pm; | ||
const float scale_width = 1.0 / ((float)full->width / size); | ||
const float scale_height = 1.0 / ((float)full->height / size); | ||
const float scale = | ||
fill ? max(scale_width, scale_height) : min(scale_width, scale_height); | ||
size_t thumb_width = scale * full->width; | ||
size_t thumb_height = scale * full->height; | ||
ssize_t offset_x, offset_y; | ||
|
||
if (fill) { | ||
offset_x = size / 2 - thumb_width / 2; | ||
offset_y = size / 2 - thumb_height / 2; | ||
thumb_width = size; | ||
thumb_height = size; | ||
} else { | ||
offset_x = 0; | ||
offset_y = 0; | ||
} | ||
|
||
*params = (struct thumbnail_params) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to create temporary instance of |
||
.thumb_width = thumb_width, | ||
.thumb_height = thumb_height, | ||
.offset_x = offset_x, | ||
.offset_y = offset_y, | ||
.fill = fill, | ||
.antialias = antialias, | ||
.scale = scale, | ||
}; | ||
} | ||
|
||
bool thumbnail_save(const struct pixmap* thumb, const char* source, | ||
const struct thumbnail_params* params) | ||
{ | ||
FILE* fp = NULL; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Useless initialization, it looks like this: FILE* fp;
fp = NULL;
fp = fopen(... |
||
uint32_t i; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
char* path = NULL; | ||
|
||
if (!get_thumb_path(&path, source)) { | ||
goto fail; | ||
} | ||
|
||
if (!make_directories(path)) { | ||
goto fail; | ||
} | ||
|
||
if (!(fp = fopen(path, "wb"))) { | ||
goto fail; | ||
} | ||
|
||
if (fprintf(fp, "P6\n%zu %zu\n255\n", thumb->width, thumb->height) < 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to use PNM format, then please move load/save function to the pnm.c. |
||
goto fail; | ||
} | ||
|
||
/* comment to store params */ | ||
if (fputc('#', fp) == EOF || | ||
fwrite(params, sizeof(struct thumbnail_params), 1, fp) != 1 || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a binary data struct, It may contain many |
||
fputc('\n', fp) == EOF) { | ||
goto fail; | ||
} | ||
|
||
// TODO: add alpha channel | ||
for (i = 0; i < thumb->width * thumb->height; ++i) { | ||
uint8_t color[] = { (((thumb->data[i] >> (8 * 2)) & 0xff)), | ||
(((thumb->data[i] >> (8 * 1)) & 0xff)), | ||
(((thumb->data[i] >> (8 * 0)) & 0xff)) }; | ||
fwrite(color, 3, 1, fp); | ||
} | ||
|
||
free(path); | ||
fclose(fp); | ||
return true; | ||
|
||
fail: | ||
free(path); | ||
if (fp) { | ||
fclose(fp); | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Allocate/reallocate pixel map. | ||
* @param thumb pixmap context | ||
* @param path path to load from | ||
* @return true pixmap was loaded | ||
*/ | ||
bool thumbnail_load(struct pixmap* thumb, const char* source, | ||
const struct thumbnail_params* params) | ||
{ | ||
FILE* fp = NULL; | ||
char* path = NULL; | ||
uint32_t i; | ||
struct stat attr_img, attr_thumb; | ||
struct thumbnail_params saved_params; | ||
|
||
if (!get_thumb_path(&path, source) || stat(source, &attr_img) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now we have many thumbnail parameters to save/load, so it would be better to have one place for all of them. Lets store the date/time in |
||
stat(path, &attr_thumb) || | ||
difftime(attr_img.st_ctime, attr_thumb.st_ctime) > 0) { | ||
goto fail; | ||
} | ||
|
||
if (!(fp = fopen(path, "rb"))) { | ||
goto fail; | ||
} | ||
|
||
char header[3]; | ||
if (fscanf(fp, "%2s\n%zu %zu\n255\n", header, &thumb->width, | ||
&thumb->height) != 3) { | ||
goto fail; | ||
Rentib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if (strcmp(header, "P6")) { | ||
goto fail; | ||
} | ||
|
||
/* comment with stored params */ | ||
if (fgetc(fp) != '#' || | ||
fread(&saved_params, sizeof(struct thumbnail_params), 1, fp) != 1 || | ||
fgetc(fp) != '\n') { | ||
goto fail; | ||
} | ||
|
||
if (memcmp(params, &saved_params, sizeof(struct thumbnail_params))) { | ||
goto fail; | ||
} | ||
|
||
if (!pixmap_create(thumb, thumb->width, thumb->height)) { | ||
goto fail; | ||
} | ||
|
||
for (i = 0; i < thumb->width * thumb->height; ++i) { | ||
uint8_t color[3]; | ||
if (fread(color, 3, 1, fp) != 1) { | ||
pixmap_free(thumb); | ||
goto fail; | ||
} | ||
thumb->data[i] = ARGB(0xff, color[0], color[1], color[2]); | ||
} | ||
|
||
free(path); | ||
fclose(fp); | ||
return true; | ||
|
||
fail: | ||
free(path); | ||
if (fp) { | ||
fclose(fp); | ||
} | ||
return false; | ||
} | ||
|
||
bool thumbnail_create(struct pixmap* thumb, const struct image* image, | ||
const struct thumbnail_params* params) | ||
{ | ||
const struct pixmap* full = &image->frames[0].pm; | ||
enum pixmap_scale scaler; | ||
|
||
if (params->antialias) { | ||
scaler = (params->scale > 1.0) ? pixmap_bicubic : pixmap_average; | ||
} else { | ||
scaler = pixmap_nearest; | ||
} | ||
|
||
// create thumbnail | ||
if (!pixmap_create(thumb, params->thumb_width, params->thumb_height)) { | ||
return false; | ||
} | ||
pixmap_scale(scaler, full, thumb, params->offset_x, params->offset_y, | ||
params->scale, image->alpha); | ||
|
||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be a part of the static context for the thumbnail entity.