| /* |
| * 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; |
| } |