• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 21335896708

25 Jan 2026 04:33PM UTC coverage: 76.581% (-2.3%) from 78.904%
21335896708

push

github

saitoha
meson: set build type to plain

20012 of 44638 branches covered (44.83%)

36354 of 47471 relevant lines covered (76.58%)

13461842.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.19
/src/filter-sample.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7
 * this software and associated documentation files (the "Software"), to deal in
8
 * the Software without restriction, including without limitation the rights to
9
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
 * the Software, and to permit persons to whom the Software is furnished to do so,
11
 * subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 */
23

24
#if defined(HAVE_CONFIG_H)
25
#include "config.h"
26
#endif
27

28
/* STDC_HEADERS */
29
#include <limits.h>
30
#include <stdint.h>
31
#include <stdlib.h>
32
#include <string.h>
33

34
#include <sixel.h>
35

36
#include "filter-sample.h"
37
#include "filter.h"
38
#include "pixelformat.h"
39

40
typedef struct sixel_filter_sample_state {
41
    sixel_filter_sample_config_t config;
42
} sixel_filter_sample_state_t;
43

44
static size_t
45
sixel_filter_sample_select_stride(
117✔
46
        sixel_filter_sample_config_t const *config,
47
        int width,
48
        int height)
49
{
50
    size_t stride;
77✔
51
    size_t base_target;
77✔
52
    size_t color_budget;
77✔
53
    size_t target;
77✔
54
    size_t total;
77✔
55

56
    stride = 1u;
117✔
57
    base_target = 4096u;
117✔
58
    color_budget = 0u;
117✔
59
    target = base_target;
117✔
60
    total = 0u;
117✔
61

62
    if (width <= 0 || height <= 0) {
117!
63
        return stride;
64
    }
65

66
    if (config != NULL && config->palette_sample_override != 0) {
117!
67
        target = config->palette_sample_target;
12✔
68
    } else {
3✔
69
        if (config != NULL && config->reqcolors > 0) {
105!
70
            color_budget = (size_t)config->reqcolors * 64u;
95✔
71
            if (color_budget / 64u != (size_t)config->reqcolors) {
95!
72
                color_budget = base_target;
73
            }
74
            if (color_budget > target) {
95✔
75
                target = color_budget;
93✔
76
            }
27✔
77
        }
29✔
78

79
        if (config != NULL
123!
80
                && (config->quality_mode == SIXEL_QUALITY_HIGH
105!
81
                    || config->quality_mode == SIXEL_QUALITY_HIGHCOLOR)) {
105✔
82
            if (target <= SIZE_MAX / 2u) {
5!
83
                target *= 2u;
5✔
84
            } else {
2✔
85
                target = SIZE_MAX;
86
            }
87
        } else if (config != NULL
102!
88
                && config->quality_mode == SIXEL_QUALITY_FULL) {
100!
89
            if (target <= SIZE_MAX / 4u) {
3!
90
                target *= 4u;
3✔
91
            } else {
92
                target = SIZE_MAX;
93
            }
94
        }
95
    }
96

97
    total = (size_t)width * (size_t)height;
117✔
98
    while (stride < total && total / (stride * stride) > target) {
491!
99
        ++stride;
374✔
100
    }
101

102
    return stride;
54✔
103
}
36✔
104

105
static SIXELSTATUS
106
sixel_filter_sample_copy_frame(
117✔
107
        sixel_filter_sample_config_t const *config,
108
        sixel_frame_t *frame,
109
        sixel_allocator_t *allocator,
110
        sixel_frame_t **sample_out,
111
        sixel_logger_t *logger,
112
        int *sample_width_out,
113
        int *sample_height_out)
114
{
115
    SIXELSTATUS status;
77✔
116
    sixel_frame_t *sample;
77✔
117
    unsigned char *src_pixels;
77✔
118
    unsigned char *dst_pixels;
77✔
119
    size_t stride;
77✔
120
    int clip_x;
77✔
121
    int clip_y;
77✔
122
    int clip_w;
77✔
123
    int clip_h;
77✔
124
    int src_width;
77✔
125
    int src_height;
77✔
126
    int width;
77✔
127
    int height;
77✔
128
    int depth;
77✔
129
    int sample_width;
77✔
130
    int sample_height;
77✔
131
    size_t sample_count;
77✔
132
    size_t payload_size;
77✔
133
    size_t dst_index;
77✔
134
    size_t src_offset;
77✔
135
    int x;
77✔
136
    int y;
77✔
137

138
    status = SIXEL_FALSE;
117✔
139
    sample = NULL;
117✔
140
    src_pixels = NULL;
117✔
141
    dst_pixels = NULL;
117✔
142
    stride = 1u;
117✔
143
    clip_x = 0;
117✔
144
    clip_y = 0;
117✔
145
    clip_w = 0;
117✔
146
    clip_h = 0;
117✔
147
    src_width = 0;
117✔
148
    src_height = 0;
117✔
149
    width = 0;
117✔
150
    height = 0;
117✔
151
    depth = 0;
117✔
152
    sample_width = 0;
117✔
153
    sample_height = 0;
117✔
154
    sample_count = 0u;
117✔
155
    payload_size = 0u;
117✔
156
    dst_index = 0u;
117✔
157
    src_offset = 0u;
117✔
158
    x = 0;
117✔
159
    y = 0;
117✔
160

161
    if (frame == NULL || sample_out == NULL) {
117!
162
        return SIXEL_BAD_ARGUMENT;
163
    }
164
    if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
117!
165
        return SIXEL_FEATURE_ERROR;
166
    }
167

168
    src_pixels = sixel_frame_get_pixels(frame);
117✔
169
    src_width = sixel_frame_get_width(frame);
117✔
170
    src_height = sixel_frame_get_height(frame);
117✔
171
    depth = sixel_helper_compute_depth(sixel_frame_get_pixelformat(frame));
117✔
172

173
    if (depth <= 0 || src_pixels == NULL) {
117!
174
        return SIXEL_BAD_ARGUMENT;
175
    }
176

177
    /*
178
     * The logger is currently unused, but it will be wired once planner
179
     * driven progress messages are emitted from the filter layer.
180
     */
181
    (void)logger;
99✔
182

183
    if (config != NULL) {
117!
184
        clip_x = config->clip_x;
117✔
185
        clip_y = config->clip_y;
117✔
186
        clip_w = config->clip_width;
117✔
187
        clip_h = config->clip_height;
117✔
188
    }
36✔
189

190
    if (clip_w <= 0 || clip_h <= 0) {
117!
191
        clip_x = 0;
48✔
192
        clip_y = 0;
48✔
193
        clip_w = src_width;
48✔
194
        clip_h = src_height;
48✔
195
    } else {
33✔
196
        if (clip_w + clip_x > src_width) {
12!
197
            if (clip_x > src_width) {
×
198
                clip_w = 0;
199
            } else {
200
                clip_w = src_width - clip_x;
×
201
            }
202
        }
203

204
        if (clip_h + clip_y > src_height) {
12!
205
            if (clip_y > src_height) {
×
206
                clip_h = 0;
207
            } else {
208
                clip_h = src_height - clip_y;
×
209
            }
210
        }
211

212
        if (clip_w <= 0 || clip_h <= 0) {
12!
213
            return SIXEL_BAD_ARGUMENT;
214
        }
215
    }
216

217
    width = clip_w;
117✔
218
    height = clip_h;
117✔
219

220
    stride = sixel_filter_sample_select_stride(config, width, height);
117✔
221
    sample_width = (width + (int)stride - 1) / (int)stride;
117✔
222
    sample_height = (height + (int)stride - 1) / (int)stride;
117✔
223
    if (sample_width <= 0 || sample_height <= 0) {
117!
224
        return SIXEL_BAD_ARGUMENT;
225
    }
226

227
    if (sample_width_out != NULL) {
117!
228
        *sample_width_out = sample_width;
×
229
    }
230
    if (sample_height_out != NULL) {
117✔
231
        *sample_height_out = sample_height;
24✔
232
    }
6✔
233

234
    sample_count = (size_t)sample_width * (size_t)sample_height;
117✔
235
    if (sample_count != 0u
117!
236
            && sample_count / (size_t)sample_height != (size_t)sample_width) {
117!
237
        return SIXEL_RUNTIME_ERROR;
238
    }
239

240
    payload_size = sample_count * (size_t)depth;
117✔
241
    if (sample_count != 0u && payload_size / sample_count != (size_t)depth) {
117!
242
        return SIXEL_RUNTIME_ERROR;
243
    }
244

245
    status = sixel_frame_new(&sample, allocator);
117✔
246
    if (SIXEL_FAILED(status)) {
117!
247
        return status;
248
    }
249
    dst_pixels = (unsigned char *)sixel_allocator_malloc(allocator,
153✔
250
                                                         payload_size);
36✔
251
    if (dst_pixels == NULL) {
117!
252
        sixel_frame_unref(sample);
×
253
        return SIXEL_BAD_ALLOCATION;
×
254
    }
255

256
    sample->pixels.u8ptr = dst_pixels;
117✔
257
    sample->width = sample_width;
117✔
258
    sample->height = sample_height;
117✔
259
    sample->pixelformat = sixel_frame_get_pixelformat(frame);
117✔
260
    sample->colorspace = sixel_frame_get_colorspace(frame);
117✔
261
    sample->ncolors = (-1);
117✔
262

263
    dst_index = 0u;
117✔
264
    for (y = 0; y < height; y += (int)stride) {
4,783✔
265
        for (x = 0; x < width; x += (int)stride) {
488,122✔
266
            src_offset = ((size_t)(clip_y + y) * (size_t)src_width
576,762✔
267
                       + (size_t)(clip_x + x))
483,456✔
268
                       * (size_t)depth;
483,456✔
269
            memcpy(dst_pixels + dst_index * (size_t)depth,
484,758✔
270
                   src_pixels + src_offset,
391,452✔
271
                   (size_t)depth);
1,302✔
272
            ++dst_index;
483,456✔
273
        }
93,306✔
274

275
    }
1,327✔
276

277
    *sample_out = sample;
117✔
278

279
    return SIXEL_OK;
117✔
280
}
36✔
281

282
SIXELSTATUS
283
sixel_filter_sample_frame(const sixel_filter_sample_config_t *config,
93✔
284
                          sixel_frame_t *frame,
285
                          sixel_allocator_t *allocator,
286
                          sixel_frame_t **sample_out,
287
                          sixel_logger_t *logger)
288
{
289
    SIXELSTATUS status;
63✔
290

291
    status = sixel_filter_sample_copy_frame(config, frame, allocator,
123✔
292
                                            sample_out, logger, NULL, NULL);
30✔
293

294
    return status;
105✔
295
}
12✔
296

297
static SIXELSTATUS
298
sixel_filter_sample_apply(sixel_filter_t *filter,
24✔
299
                          sixel_allocator_t *allocator,
300
                          sixel_logger_t *logger)
301
{
302
    SIXELSTATUS status;
14✔
303
    sixel_filter_sample_state_t *state;
14✔
304
    sixel_frame_t *input_frame;
14✔
305
    sixel_frame_t *sample;
14✔
306
    int sample_height;
14✔
307

308
    if (filter == NULL || allocator == NULL) {
24!
309
        return SIXEL_BAD_ARGUMENT;
310
    }
311

312
    state = (sixel_filter_sample_state_t *)filter->userdata;
24✔
313
    if (state == NULL) {
24!
314
        return SIXEL_BAD_ARGUMENT;
315
    }
316

317
    if (filter->input.slot == NULL || filter->output.slot == NULL) {
24!
318
        return SIXEL_BAD_ARGUMENT;
319
    }
320

321
    input_frame = *(filter->input.slot);
24✔
322
    if (input_frame == NULL) {
24!
323
        return SIXEL_BAD_ARGUMENT;
324
    }
325

326
    if (*(filter->output.slot) != NULL) {
24!
327
        sixel_frame_unref(*(filter->output.slot));
×
328
        *(filter->output.slot) = NULL;
×
329
    }
330

331
    sample_height = 0;
24✔
332
    status = sixel_filter_sample_copy_frame(&state->config, input_frame,
30✔
333
                                            allocator, &sample, logger,
6✔
334
                                            NULL, &sample_height);
335
    if (SIXEL_SUCCEEDED(status)) {
24!
336
        *(filter->output.slot) = sample;
24✔
337
        if (sample_height > 0) {
24!
338
            filter->progress.total_units = sample_height;
24✔
339
            filter->progress.completed_units = sample_height;
24✔
340
            sixel_filter_update_progress(filter, sample_height);
24✔
341
        }
6✔
342
    }
6✔
343

344
    return status;
12✔
345
}
6✔
346

347
static void
348
sixel_filter_sample_dispose(sixel_filter_t *filter)
24✔
349
{
350
    sixel_filter_sample_state_t *state;
14✔
351

352
    if (filter == NULL) {
24!
353
        return;
354
    }
355

356
    state = (sixel_filter_sample_state_t *)filter->userdata;
24✔
357
    if (state != NULL) {
24!
358
        free(state);
24✔
359
    }
6✔
360
}
6!
361

362
SIXELSTATUS
363
sixel_filter_sample_init(sixel_filter_t *filter,
24✔
364
                         const sixel_filter_sample_config_t *config)
365
{
366
    SIXELSTATUS status;
14✔
367
    sixel_filter_sample_state_t *state;
14✔
368

369
    if (filter == NULL || config == NULL) {
24!
370
        return SIXEL_BAD_ARGUMENT;
371
    }
372

373
    state = (sixel_filter_sample_state_t *)malloc(sizeof(*state));
24✔
374
    if (state == NULL) {
24!
375
        return SIXEL_BAD_ALLOCATION;
376
    }
377

378
    state->config = *config;
24✔
379

380
    status = sixel_filter_init(filter,
30✔
381
                               "sample",
382
                               SIXEL_FILTER_KIND_SAMPLE,
383
                               sixel_filter_sample_apply,
384
                               sixel_filter_sample_dispose,
385
                               state);
6✔
386
    if (SIXEL_FAILED(status)) {
24!
387
        free(state);
×
388
    }
389

390
    return status;
12✔
391
}
6✔
392

393
/* emacs Local Variables:      */
394
/* emacs mode: c               */
395
/* emacs tab-width: 4          */
396
/* emacs indent-tabs-mode: nil */
397
/* emacs c-basic-offset: 4     */
398
/* emacs End:                  */
399
/* vim: set expandtab ts=4 sts=4 sw=4 : */
400
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc