
Apply patch from upstream 9c4f5f11802c 'Adjust shadow to equally darken and lighten'. The waved flags were noticeably darker than the source image, for example the blue of the Russian flag was too dark.
452 lines
12 KiB
C
452 lines
12 KiB
C
/*
|
|
* Copyright 2014 Google Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* Google contributors: Behdad Esfahbod
|
|
*/
|
|
|
|
#include <cairo.h>
|
|
#include <libgen.h> // basename
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
|
|
#define SCALE 8
|
|
#define SIZE 128
|
|
#define MARGIN (debug ? 4 : 0)
|
|
|
|
static unsigned int debug;
|
|
|
|
#define std_aspect (5./3.)
|
|
#define top 21
|
|
#define bot 128-top
|
|
#define B 21
|
|
#define C 4
|
|
static struct { double x, y; } mesh_points[] =
|
|
{
|
|
{ 1, top+C},
|
|
{ 43, top-B+C},
|
|
{ 85, top+B-C},
|
|
{127, top-C},
|
|
{127, bot-C},
|
|
{ 85, bot+B-C},
|
|
{ 43, bot-B+C},
|
|
{ 1, bot+C},
|
|
};
|
|
#define M(i) \
|
|
x_aspect (mesh_points[i].x, aspect), \
|
|
y_aspect (mesh_points[i].y, aspect)
|
|
|
|
static inline double x_aspect (double v, double aspect)
|
|
{
|
|
return aspect >= 1. ? v : (v - 64) * aspect + 64;
|
|
}
|
|
static inline double y_aspect (double v, double aspect)
|
|
{
|
|
return aspect <= 1. ? v : (v - 64) / aspect + 64;
|
|
}
|
|
|
|
static cairo_path_t *
|
|
wave_path_create (double aspect)
|
|
{
|
|
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0,0);
|
|
cairo_t *cr = cairo_create (surface);
|
|
cairo_path_t *path;
|
|
|
|
cairo_scale (cr, SIZE/128.*SCALE, SIZE/128.*SCALE);
|
|
|
|
cairo_line_to(cr, M(0));
|
|
cairo_curve_to(cr, M(1), M(2), M(3));
|
|
cairo_line_to(cr, M(4));
|
|
cairo_curve_to(cr, M(5), M(6), M(7));
|
|
cairo_close_path (cr);
|
|
|
|
cairo_identity_matrix (cr);
|
|
path = cairo_copy_path (cr);
|
|
cairo_destroy (cr);
|
|
cairo_surface_destroy (surface);
|
|
|
|
return path;
|
|
}
|
|
|
|
static cairo_pattern_t *
|
|
wave_mesh_create (double aspect, int alpha)
|
|
{
|
|
cairo_pattern_t *pattern = cairo_pattern_create_mesh();
|
|
cairo_matrix_t scale_matrix = {128./SIZE/SCALE, 0, 0, 128./SIZE/SCALE, 0, 0};
|
|
cairo_pattern_set_matrix (pattern, &scale_matrix);
|
|
cairo_mesh_pattern_begin_patch(pattern);
|
|
|
|
cairo_mesh_pattern_line_to(pattern, M(0));
|
|
cairo_mesh_pattern_curve_to(pattern, M(1), M(2), M(3));
|
|
cairo_mesh_pattern_line_to(pattern, M(4));
|
|
cairo_mesh_pattern_curve_to(pattern, M(5), M(6), M(7));
|
|
|
|
if (alpha)
|
|
{
|
|
cairo_mesh_pattern_set_corner_color_rgba(pattern, 0, 1, 1, 1, .5);
|
|
cairo_mesh_pattern_set_corner_color_rgba(pattern, 1,.5,.5,.5, .5);
|
|
cairo_mesh_pattern_set_corner_color_rgba(pattern, 2, 0, 0, 0, .5);
|
|
cairo_mesh_pattern_set_corner_color_rgba(pattern, 3,.5,.5,.5, .5);
|
|
}
|
|
else
|
|
{
|
|
cairo_mesh_pattern_set_corner_color_rgb(pattern, 0, 0, 0, .5);
|
|
cairo_mesh_pattern_set_corner_color_rgb(pattern, 1, 1, 0, .5);
|
|
cairo_mesh_pattern_set_corner_color_rgb(pattern, 2, 1, 1, .5);
|
|
cairo_mesh_pattern_set_corner_color_rgb(pattern, 3, 0, 1, .5);
|
|
}
|
|
|
|
cairo_mesh_pattern_end_patch(pattern);
|
|
|
|
return pattern;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
scale_flag (cairo_surface_t *flag)
|
|
{
|
|
unsigned int w = cairo_image_surface_get_width (flag);
|
|
unsigned int h = cairo_image_surface_get_height (flag);
|
|
cairo_surface_t *scaled = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 256,256);
|
|
cairo_t *cr = cairo_create (scaled);
|
|
|
|
cairo_scale (cr, 256./w, 256./h);
|
|
|
|
cairo_set_source_surface (cr, flag, 0, 0);
|
|
cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_BEST);
|
|
cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
|
|
cairo_paint (cr);
|
|
|
|
cairo_destroy (cr);
|
|
return scaled;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
load_scaled_flag (const char *filename, double *aspect)
|
|
{
|
|
cairo_surface_t *flag = cairo_image_surface_create_from_png (filename);
|
|
cairo_surface_t *scaled = scale_flag (flag);
|
|
*aspect = (double) cairo_image_surface_get_width (flag) /
|
|
(double) cairo_image_surface_get_height (flag);
|
|
cairo_surface_destroy (flag);
|
|
return scaled;
|
|
}
|
|
|
|
static int
|
|
is_transparent (uint32_t pix)
|
|
{
|
|
return ((pix>>24) < 0xff);
|
|
}
|
|
|
|
static int
|
|
border_is_transparent (cairo_surface_t *scaled_flag)
|
|
{
|
|
/* Some flags might have a border already. As such, skip
|
|
* a few pixels on each side... */
|
|
const unsigned int skip = 5;
|
|
uint32_t *s = (uint32_t *) cairo_image_surface_get_data (scaled_flag);
|
|
unsigned int width = cairo_image_surface_get_width (scaled_flag);
|
|
unsigned int height = cairo_image_surface_get_height (scaled_flag);
|
|
unsigned int sstride = cairo_image_surface_get_stride (scaled_flag) / 4;
|
|
|
|
int transparent = 0;
|
|
|
|
assert (width > 2 * skip && height > 2 * skip);
|
|
|
|
|
|
for (unsigned int x = skip; x < width - skip; x++)
|
|
transparent |= is_transparent (s[x]);
|
|
s += sstride;
|
|
for (unsigned int y = 1 + skip; y < height - 1 - skip; y++)
|
|
{
|
|
transparent |= is_transparent (s[skip]);
|
|
transparent |= is_transparent (s[width - 1 - skip]);
|
|
s += sstride;
|
|
}
|
|
for (unsigned int x = skip; x < width - skip; x++)
|
|
transparent |= is_transparent (s[x]);
|
|
|
|
return transparent;
|
|
}
|
|
|
|
static cairo_t *
|
|
create_image (void)
|
|
{
|
|
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
(SIZE+2*MARGIN)*SCALE,
|
|
(SIZE+2*MARGIN)*SCALE);
|
|
cairo_t *cr = cairo_create (surface);
|
|
cairo_surface_destroy (surface);
|
|
return cr;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
wave_surface_create (double aspect)
|
|
{
|
|
cairo_t *cr = create_image ();
|
|
cairo_surface_t *surface = cairo_surface_reference (cairo_get_target (cr));
|
|
cairo_pattern_t *mesh = wave_mesh_create (aspect, 0);
|
|
cairo_set_source (cr, mesh);
|
|
cairo_paint (cr);
|
|
cairo_pattern_destroy (mesh);
|
|
cairo_destroy (cr);
|
|
return surface;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
texture_map (cairo_surface_t *src, cairo_surface_t *tex)
|
|
{
|
|
uint32_t *s = (uint32_t *) cairo_image_surface_get_data (src);
|
|
unsigned int width = cairo_image_surface_get_width (src);
|
|
unsigned int height = cairo_image_surface_get_height (src);
|
|
unsigned int sstride = cairo_image_surface_get_stride (src) / 4;
|
|
|
|
cairo_surface_t *dst = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
|
|
uint32_t *d = (uint32_t *) cairo_image_surface_get_data (dst);
|
|
unsigned int dstride = cairo_image_surface_get_stride (dst) / 4;
|
|
|
|
uint32_t *t = (uint32_t *) cairo_image_surface_get_data (tex);
|
|
unsigned int twidth = cairo_image_surface_get_width (tex);
|
|
unsigned int theight = cairo_image_surface_get_height (tex);
|
|
unsigned int tstride = cairo_image_surface_get_stride (tex) / 4;
|
|
|
|
assert (twidth == 256 && theight == 256);
|
|
|
|
for (unsigned int y = 0; y < height; y++)
|
|
{
|
|
for (unsigned int x = 0; x < width; x++)
|
|
{
|
|
unsigned int pix = s[x];
|
|
unsigned int sa = pix >> 24;
|
|
unsigned int sr = (pix >> 16) & 0xFF;
|
|
unsigned int sg = (pix >> 8) & 0xFF;
|
|
unsigned int sb = (pix ) & 0xFF;
|
|
if (sa == 0)
|
|
{
|
|
d[x] = 0;
|
|
continue;
|
|
}
|
|
if (sa != 255)
|
|
{
|
|
sr = sr * 255 / sa;
|
|
sg = sg * 255 / sa;
|
|
sb = sb * 255 / sa;
|
|
}
|
|
assert (sb >= 127 && sb <= 129);
|
|
d[x] = t[tstride * sg + sr];
|
|
}
|
|
s += sstride;
|
|
d += dstride;
|
|
}
|
|
cairo_surface_mark_dirty (dst);
|
|
|
|
return dst;
|
|
}
|
|
|
|
static void
|
|
wave_flag (const char *filename, const char *out_prefix)
|
|
{
|
|
static cairo_path_t *standard_wave_path;
|
|
static cairo_surface_t *standard_wave_surface;
|
|
cairo_path_t *wave_path;
|
|
cairo_surface_t *wave_surface;
|
|
int border_transparent;
|
|
char out[1000];
|
|
double aspect = 0;
|
|
|
|
cairo_surface_t *scaled_flag, *waved_flag;
|
|
cairo_t *cr;
|
|
|
|
if (debug) printf ("Processing %s\n", filename);
|
|
|
|
scaled_flag = load_scaled_flag (filename, &aspect);
|
|
|
|
aspect /= std_aspect;
|
|
aspect = sqrt (aspect); // Discount the effect
|
|
if (.9 <= aspect && aspect <= 1.1)
|
|
{
|
|
if (debug) printf ("Standard aspect ratio\n");
|
|
aspect = 1.;
|
|
}
|
|
|
|
if (aspect == 1.)
|
|
{
|
|
if (!standard_wave_path)
|
|
standard_wave_path = wave_path_create (aspect);
|
|
if (!standard_wave_surface)
|
|
standard_wave_surface = wave_surface_create (aspect);
|
|
wave_path = standard_wave_path;
|
|
wave_surface = standard_wave_surface;
|
|
}
|
|
else
|
|
{
|
|
wave_path = wave_path_create (aspect);
|
|
wave_surface = wave_surface_create (aspect);
|
|
}
|
|
|
|
|
|
border_transparent = border_is_transparent (scaled_flag);
|
|
waved_flag = texture_map (wave_surface, scaled_flag);
|
|
cairo_surface_destroy (scaled_flag);
|
|
|
|
cr = create_image ();
|
|
cairo_translate (cr, SCALE * MARGIN, SCALE * MARGIN);
|
|
|
|
// Paint waved flag
|
|
cairo_set_source_surface (cr, waved_flag, 0, 0);
|
|
cairo_append_path (cr, wave_path);
|
|
if (!debug)
|
|
cairo_clip_preserve (cr);
|
|
cairo_paint (cr);
|
|
|
|
// Paint border
|
|
if (!border_transparent)
|
|
{
|
|
double border_alpha = .2;
|
|
double border_width = 4 * SCALE;
|
|
double border_gray = 0x42/255.;
|
|
if (debug)
|
|
printf ("Border: alpha %g width %g gray %g\n",
|
|
border_alpha, border_width/SCALE, border_gray);
|
|
|
|
cairo_save (cr);
|
|
cairo_set_source_rgba (cr,
|
|
border_gray * border_alpha,
|
|
border_gray * border_alpha,
|
|
border_gray * border_alpha,
|
|
border_alpha);
|
|
cairo_set_line_width (cr, 2*border_width);
|
|
if (!debug)
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_MULTIPLY);
|
|
cairo_stroke (cr);
|
|
cairo_restore (cr);
|
|
}
|
|
else
|
|
{
|
|
if (debug) printf ("Transparent border\n");
|
|
cairo_new_path (cr);
|
|
}
|
|
|
|
// Paint shade gradient
|
|
{
|
|
cairo_pattern_t *gradient = wave_mesh_create (aspect, 1);
|
|
cairo_pattern_t *w = cairo_pattern_create_for_surface (waved_flag);
|
|
|
|
cairo_save (cr);
|
|
cairo_set_source (cr, gradient);
|
|
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOFT_LIGHT);
|
|
cairo_mask (cr, w);
|
|
|
|
cairo_restore (cr);
|
|
|
|
cairo_pattern_destroy (w);
|
|
}
|
|
|
|
if (debug)
|
|
{
|
|
/* Draw mesh points. */
|
|
cairo_save (cr);
|
|
cairo_scale (cr, SIZE/128.*SCALE, SIZE/128.*SCALE);
|
|
cairo_set_source_rgba (cr, .5,.0,.0,.9);
|
|
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
|
|
for (unsigned int i = 0; i < sizeof (mesh_points) / sizeof (mesh_points[0]); i++)
|
|
{
|
|
cairo_move_to (cr, M(i));
|
|
cairo_rel_line_to (cr, 0, 0);
|
|
}
|
|
cairo_set_line_width (cr, 2);
|
|
cairo_stroke (cr);
|
|
for (unsigned int i = 0; i < 4; i++)
|
|
{
|
|
cairo_move_to (cr, M(2*i));
|
|
cairo_line_to (cr, M(2*i+1));
|
|
cairo_move_to (cr, M(2*i));
|
|
cairo_line_to (cr, M(7 - 2*i));
|
|
}
|
|
cairo_set_line_width (cr, .5);
|
|
cairo_stroke (cr);
|
|
cairo_restore (cr);
|
|
}
|
|
|
|
if (!debug)
|
|
{
|
|
/* Scale down, 2x at a time, to get best downscaling, because cairo's
|
|
* downscaling is crap... :( */
|
|
unsigned int scale = SCALE;
|
|
while (scale > 1)
|
|
{
|
|
cairo_surface_t *old_surface, *new_surface;
|
|
|
|
old_surface = cairo_surface_reference (cairo_get_target (cr));
|
|
assert (scale % 2 == 0);
|
|
scale /= 2;
|
|
cairo_destroy (cr);
|
|
new_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, (SIZE+2*MARGIN)*scale, (SIZE+2*MARGIN)*scale);
|
|
cr = cairo_create (new_surface);
|
|
cairo_scale (cr, .5, .5);
|
|
cairo_set_source_surface (cr, old_surface, 0, 0);
|
|
cairo_paint (cr);
|
|
cairo_surface_destroy (old_surface);
|
|
cairo_surface_destroy (new_surface);
|
|
}
|
|
}
|
|
|
|
*out = '\0';
|
|
strcat (out, out_prefix);
|
|
// diff from upstream. we call this a bit differently, filename might not be in cwd.
|
|
|
|
// basename wants a non-const argument. The problem here is paths that end in a
|
|
// slash, POSIX basename removes them while GNU just returns a pointer to that
|
|
// slash. Since this is supposed to be a filename such input is illegal for us.
|
|
// We're already not checking for overflow of the output buffer anyway...
|
|
strcat (out, basename((char *) filename));
|
|
|
|
cairo_surface_write_to_png (cairo_get_target (cr), out);
|
|
cairo_destroy (cr);
|
|
if (wave_path != standard_wave_path)
|
|
cairo_path_destroy (wave_path);
|
|
if (wave_surface != standard_wave_surface)
|
|
cairo_surface_destroy (wave_surface);
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
const char *out_prefix;
|
|
|
|
if (argc < 3)
|
|
{
|
|
fprintf (stderr, "Usage: waveflag [-debug] out-prefix [in.png]...\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!strcmp (argv[1], "-debug"))
|
|
{
|
|
debug = 1;
|
|
argc--, argv++;
|
|
}
|
|
|
|
out_prefix = argv[1];
|
|
argc--, argv++;
|
|
|
|
for (argc--, argv++; argc; argc--, argv++)
|
|
wave_flag (*argv, out_prefix);
|
|
|
|
return 0;
|
|
}
|