Configuring a context¶
A context carries its settings next to the image: thirteen ph_context_set_*()
functions, each with a default that a freshly created context already has. This page lists
all of them, says when each one is read, which ones change the value of a hash, and how to
give every context in a program, and every worker of a batch, the same configuration. What
a setting does to a particular hash, measured, is on that algorithm's page.
Every setting¶
Eight settings are read by the hash, and only the algorithms they name read them.
| Setter | Sets | Accepted | Default |
|---|---|---|---|
ph_context_set_gray_weights() |
the weights of R, G and B in the grayscale of aHash, dHash, pHash, wHash, mHash, BMH and Radial | each ≥ 0, sum 1 … 8 421 504 | 38, 75, 15 |
ph_context_set_gamma() |
Radial's gamma, and nothing else's | finite, (0.001, 1000] | 1, the step skipped |
ph_context_set_phash_params() |
pHash's DCT grid and block of coefficients | dct_size from reduction_size to 32; reduction_size 4 … 8 |
32, 8 |
ph_context_set_whash_mode() |
wHash's mode | PH_WHASH_FAST, PH_WHASH_FULL |
PH_WHASH_FAST |
ph_context_set_whash_remove_max_haar_ll() |
wHash's removal of the coarsest LL band | any int; non-zero is on |
off |
ph_context_set_mhash_params() |
mHash's kernel scale and normalized size | alpha > 1, level ≥ 0, a kernel of at most 65 on a side; size 62 … 4096 |
2, 1, 512 |
ph_context_set_block_params() |
BMH's grid, and with it the digest's size | 2 … 32 | 16 |
ph_context_set_radial_params() |
Radial's projections, samples and blur | 40 … 4096, 2 … 4096, σ in (0, 64/3] | 180, 128, 3.5 |
Five are read by the load, and reach every algorithm through the image they load.
| Setter | Sets | Accepted | Default |
|---|---|---|---|
ph_context_set_load_grayscale() |
one channel from the decoder instead of color; ColorHash and ColorMoments refuse such an image | any int; non-zero is on |
off |
ph_context_set_alpha_mode() |
the background of an image with alpha | the four ph_alpha_mode_t values |
PH_ALPHA_BLEND_GREY |
ph_context_set_auto_orient() |
applying the EXIF orientation | any int; non-zero is on |
on |
ph_context_set_decode_scale() |
decoding a JPEG at ½, ¼ or ⅛ of its size | the four ph_decode_scale_t values |
PH_DECODE_SCALE_FULL |
ph_context_set_max_pixels() |
the largest image a load accepts | any uint64_t; 0 is no limit of its own |
256 × 1024 × 1024 |
ColorHash and ColorMoments have no parameters, so only the load settings reach them. Loading images says when to change each of the five.
When a setting is read¶
flowchart LR
S1[Load settings] --> L["ph_load_from_…()"]
L --> I[Loaded image]
I --> H["ph_compute_…()"]
S2[Hash settings] --> H
H --> R[Hash]
A hash setting is read by the next hash. The image already loaded is hashed again with the new value, without a reload. The grayscale weights are one of these: setting them drops the grayscale cached from the loaded image, and the next hash converts it again.
A load setting is read by the next load. The image in the context keeps what it was loaded with: grayscale loading switched on after a color load leaves the color image, and ColorHash still works on it until the next load replaces it.
A refused value changes nothing¶
Every setter returns PH_SUCCESS for a value in range and
PH_ERR_INVALID_ARGUMENT for anything else,
a NULL context included. A refused call leaves the whole configuration as it was: a value
is never clamped into range, a pair is never half applied, and a refusal never installs
the defaults. Three weights that sum to zero are an error, not a request for the standard
ones.
Each upper bound is a limit of the implementation: the 64 bits of a hash, the 1024 bits of a digest, the largest kernel the buffer holds. A lower bound keeps the hash meaningful: below a 4×4 block most unrelated images share a pHash, and a Radial line of one sample has no variance to measure. An unknown enum value is refused too, as a C caller or a binding can pass any integer through an enum parameter. A setter called with a constant needs no check; one called with a value read from a file, a command line or another program does, since the hashes computed after an ignored refusal use the setting that was there before.
Settings that change the hash¶
A hash or digest does not record the settings it was computed with, and neither does its text form. Two hashes are comparable only when computed with the same settings, so hashes are stored next to the configuration that made them, and recomputed when it changes. Every setting except the size limit can change a value:
- Always, for the algorithms it names: the pHash, wHash, mHash, BMH and Radial parameters, and the grayscale weights for the seven grayscale hashes. The BMH grid changes the digest's length too, except between sizes that round to the same number of bytes; the other parameters keep it, so a digest from another configuration is compared without complaint, and the number returned measures nothing.
- Only on some images: the gamma, when it is not 1; the background, on an image with an alpha channel; the orientation, on an image whose tag turns or mirrors it; the decode scale, on a JPEG read by the native decoder. A program tested on opaque, untagged PNGs sees no change from the last three, until the first image that has what they act on.
- By a level here and there: grayscale loading, on a JPEG, whose decoder rounds the luma where the library's conversion truncates (measured). Grayscale weights do nothing to an image loaded as grayscale, which has no color to weigh.
- Never:
ph_context_set_max_pixels()decides whether a load succeeds, not what it loads.
Reading the configuration back¶
ph_context_get_gray_weights() is the only
getter. The grayscale weights are the one setting stored differently from what was passed:
they are scaled to a sum of 128, the red and green shares rounded down and blue taking the
rest, so (1, 1, 1) is stored as (42, 42, 44). Every other setter stores its value as given,
a flag as on or off, so the value the program passed is the value in use.
A digest's size follows from the configuration too, and
ph_digest_info() reports it for a context without
an image: BMH's is block_size² bits rounded up to whole bytes, and every other
algorithm's is fixed.
The same settings everywhere¶
There is no function that copies one context's settings into another. A program that
hashes on several threads, where each thread has its own context, configures every
context with one function, so that a new setting cannot reach some contexts and miss
others (the configure() function in the example below).
The batch functions take the configuration from a template instead:
ph_hash_files_ex() and
ph_hash_buffers_ex() copy everything set on
options.config into every worker's context, on the calling thread, before any worker
starts; an image loaded on the template is ignored.
ph_hash_files() and
ph_hash_buffers() take no context and always use the
defaults, the default size limit included. A batch computes the four 64-bit hashes, so of
the hash settings it reads only the grayscale weights and the pHash and wHash ones
(Hashing many files).
In code¶
The example configures a context in one function, reads back what it stored, has a value refused, and shows a hash setting taking effect at the next hash and a load setting at the next load.
/* A context configured in one function, a refused value that changes nothing, the one
* setting that reads back differently, and when a setting takes effect: at the next hash
* or at the next load. Build/run:
* cc configure.c -o configure $(pkg-config --cflags --libs libphash)
* ./configure image.jpg
*/
#include <libphash.h>
#include <stdio.h>
/* Every context that computes hashes to be compared with each other is configured by
* this one function, so that they all compute them alike. Each setter returns
* PH_ERR_INVALID_ARGUMENT for a value out of range and leaves the context as it was. */
static ph_error_t configure(ph_context_t *ctx) {
ph_error_t err;
if ((err = ph_context_set_max_pixels(ctx, 50u * 1000u * 1000u)) != PH_SUCCESS ||
(err = ph_context_set_alpha_mode(ctx, PH_ALPHA_BLEND_WHITE)) != PH_SUCCESS ||
(err = ph_context_set_gray_weights(ctx, 1, 1, 1)) != PH_SUCCESS ||
(err = ph_context_set_block_params(ctx, 8)) != PH_SUCCESS) {
return err;
}
return PH_SUCCESS;
}
static int fail(ph_context_t *ctx, const char *what, ph_error_t err) {
fprintf(stderr, "%s: %s (%s)\n", what, ph_get_error_string(err),
ph_get_last_error_message(ctx));
ph_free(ctx);
return 1;
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "usage: %s <image>\n", argv[0]);
return 1;
}
ph_context_t *ctx = NULL;
if (ph_create(&ctx) != PH_SUCCESS) {
fprintf(stderr, "ph_create failed\n");
return 1;
}
ph_error_t err = configure(ctx);
if (err != PH_SUCCESS) {
return fail(ctx, "configure", err);
}
/* The grayscale weights are stored normalized to a sum of 128, so they are the one
* setting with a getter. A digest's size follows from the settings too. */
int r, g, b;
size_t bmh_size;
if (ph_context_get_gray_weights(ctx, &r, &g, &b) != PH_SUCCESS ||
ph_digest_info(ctx, PH_ALGO_BMH, &bmh_size, NULL) != PH_SUCCESS) {
return fail(ctx, "read back", PH_ERR_INVALID_ARGUMENT);
}
printf("gray weights (1, 1, 1) are stored as (%d, %d, %d)\n", r, g, b);
printf("BMH digest at block_size 8: %zu bytes\n", bmh_size);
if ((err = ph_load_from_file(ctx, argv[1])) != PH_SUCCESS) {
return fail(ctx, argv[1], err);
}
uint64_t before, after;
if ((err = ph_compute_phash(ctx, &before)) != PH_SUCCESS) {
return fail(ctx, "pHash", err);
}
/* A 9x9 block does not fit 64 bits: refused, and pHash is computed as before. */
err = ph_context_set_phash_params(ctx, 32, 9);
printf("set_phash_params(32, 9): %s\n", ph_get_error_string(err));
if ((err = ph_compute_phash(ctx, &after)) != PH_SUCCESS) {
return fail(ctx, "pHash", err);
}
int failed = 0;
if (after != before) {
printf("a refused value changed the hash\n");
failed = 1;
}
/* A hash setting is read by the next hash: no reload. */
ph_context_set_phash_params(ctx, 32, 6);
if ((err = ph_compute_phash(ctx, &after)) != PH_SUCCESS) {
return fail(ctx, "pHash", err);
}
printf("pHash, 8x8 block: %016llx\npHash, 6x6 block: %016llx\n", (unsigned long long)before,
(unsigned long long)after);
/* A load setting is read by the next load: the image in the context stays in color
* until it is loaded again. */
ph_context_set_load_grayscale(ctx, 1);
ph_digest_t color;
ph_error_t before_reload = ph_compute_color_hash(ctx, &color);
if ((err = ph_load_from_file(ctx, argv[1])) != PH_SUCCESS) {
return fail(ctx, argv[1], err);
}
ph_error_t after_reload = ph_compute_color_hash(ctx, &color);
printf("ColorHash before the reload: %s\n", ph_get_error_string(before_reload));
printf("ColorHash after the reload: %s\n", ph_get_error_string(after_reload));
ph_free(ctx);
if (r + g + b != 128 || bmh_size != 8 || after == before || before_reload != PH_SUCCESS ||
after_reload != PH_ERR_REQUIRES_COLOR) {
printf("unexpected result\n");
failed = 1;
}
return failed;
}