/* * 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, Roozbeh Pournader */ #include #include #include #include #include #include #define SCALE 8 #define SIZE 128 #define MARGIN (debug ? 24 : 0) static unsigned int debug; static cairo_path_t *wave_path_create(void) { 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, SCALE, SCALE); cairo_move_to(cr, 127.15, 81.52); cairo_rel_line_to(cr, -20.51, -66.94); cairo_rel_curve_to(cr, -0.61, -2, -2.22, -3.53, -4.25, -4.03); cairo_rel_curve_to(cr, -0.48, -0.12, -0.96, -0.18, -1.44, -0.18); cairo_rel_curve_to(cr, -1.56, 0, -3.07, 0.61, -4.2, 1.74); cairo_rel_curve_to(cr, -9.56, 9.56, -17.94, 11.38, -30.07, 11.38); cairo_rel_curve_to(cr, -3.68, 0, -7.72, -0.18, -11.99, -0.38); cairo_rel_curve_to(cr, -3.37, -0.15, -6.85, -0.31, -10.62, -0.4); cairo_rel_curve_to(cr, -0.66, -0.02, -1.31, -0.02, -1.95, -0.02); cairo_rel_curve_to(cr, -30.67, 0, -40.49, 18.56, -40.89, 19.35); cairo_rel_curve_to(cr, -0.52, 1.01, -0.73, 2.16, -0.62, 3.29); cairo_rel_line_to(cr, 6.72, 66.95); cairo_rel_curve_to(cr, 0.22, 2.22, 1.67, 4.13, 3.75, 4.95); cairo_rel_curve_to(cr, 0.7, 0.27, 1.43, 0.4, 2.16, 0.4); cairo_rel_curve_to(cr, 1.43, 0, 2.84, -0.52, 3.95, -1.5); cairo_rel_curve_to(cr, 0.1, -0.09, 12.42, -10.63, 32.13, -10.63); cairo_rel_curve_to(cr, 2.52, 0, 5.09, 0.17, 7.64, 0.51); cairo_rel_curve_to(cr, 9.27, 1.23, 16.03, 1.78, 21.95, 1.78); cairo_rel_curve_to(cr, 18.93, 0, 32.93, -6.1, 46.82, -20.38); cairo_curve_to(cr, 127.24, 85.85, 127.79, 83.59, 127.15, 81.52); cairo_close_path(cr); cairo_identity_matrix(cr); path = cairo_copy_path(cr); cairo_destroy(cr); cairo_surface_destroy(surface); return path; } static struct { double x, y; } mesh_points[] = { { -1, 43}, { 30, -3}, { 77, 47}, {104, 1}, {130, 84}, {100, 138}, { 45, 80}, { 7, 127}, }; #define M(i) mesh_points[i].x, mesh_points[i].y static cairo_pattern_t *wave_mesh_create(void) { cairo_pattern_t *pattern = cairo_pattern_create_mesh(); cairo_matrix_t scale_matrix = {1./SCALE, 0, 0, 1./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)); 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) { cairo_surface_t *flag = cairo_image_surface_create_from_png(filename); cairo_surface_t *scaled = scale_flag(flag); cairo_surface_destroy(flag); return scaled; } /* Returns 65536 for luminosoty of 1.0. */ static int luminosity(uint32_t pix) { unsigned int sr = (pix >> 16) & 0xFF; unsigned int sg = (pix >> 8) & 0xFF; unsigned int sb = pix & 0xFF; /* Apply gamma of 2.0 */ sr = sr * sr; sg = sg * sg; sb = sb * sb; return (sr * 13933u /* 0.2126 * 65536 */ + sg * 46871u /* 0.7152 * 65536 */ + sb * 4731u /* 0.0722 * 65536 */) / (255*255); } /* Returns luminosity. Only miningul if pixel is opaque. * If pixel is not opaque, sets *transparent to 1. */ static int luminosity_and_transparency(uint32_t pix, int *transparent) { if ((pix>>24) < 0xff) { *transparent = 1; return 0; } return luminosity(pix); } static double calculate_border_luminosity_and_transparency( cairo_surface_t *scaled_flag, int *transparent) { /* 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; unsigned int luma = 0; unsigned int perimeter = (2 * ((width-2*skip) + (height-2*skip) - 2)); assert(width > 2 * skip && height > 2 * skip); *transparent = 0; for (unsigned int x = skip; x < width - skip; x++) luma += luminosity_and_transparency(s[x], transparent); s += sstride; for (unsigned int y = 1 + skip; y < height - 1 - skip; y++) { luma += luminosity_and_transparency(s[skip], transparent); luma += luminosity_and_transparency(s[width - 1 - skip], transparent); s += sstride; } for (unsigned int x = skip; x < width - skip; x++) luma += luminosity_and_transparency(s[x], transparent); if (*transparent) { /* Flag is non-rectangular; eg. Nepal. * Don't draw a border. */ return 0; } return luma / (65536. * perimeter); } 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(void) { cairo_t *cr = create_image(); cairo_surface_t *surface = cairo_surface_reference(cairo_get_target(cr)); cairo_pattern_t *mesh = wave_mesh_create(); 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 *input_filename, const char *output_filename) { static cairo_path_t *wave_path; static cairo_surface_t *wave_surface; double border_luminosity; int border_transparent; cairo_surface_t *scaled_flag, *waved_flag; cairo_t *cr; if (!wave_path) wave_path = wave_path_create(); if (!wave_surface) wave_surface = wave_surface_create(); printf("Processing %s\n", input_filename); scaled_flag = load_scaled_flag(input_filename); border_luminosity = calculate_border_luminosity_and_transparency( scaled_flag, &border_transparent); waved_flag = texture_map(wave_surface, scaled_flag); cairo_surface_destroy(scaled_flag); cr = create_image(); cairo_translate(cr, SCALE * MARGIN, SCALE * MARGIN); cairo_set_source_surface(cr, waved_flag, 0, 0); cairo_append_path(cr, wave_path); if (!debug) cairo_clip_preserve(cr); cairo_paint(cr); if (!border_transparent) { double border_alpha = .5 + fabs(.5 - border_luminosity); double border_width = 3 * SCALE; double border_gray = (1 - border_luminosity) * border_alpha; 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_gray, border_gray, border_alpha); cairo_set_line_width(cr, border_width); if (!debug) cairo_set_operator(cr, CAIRO_OPERATOR_HSL_LUMINOSITY); cairo_stroke(cr); cairo_restore(cr); } else { printf("Transparent border\n"); cairo_new_path(cr); } if (debug) { /* Draw mesh points. */ cairo_save(cr); cairo_scale(cr, SCALE, 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); } } cairo_surface_write_to_png(cairo_get_target(cr), output_filename); cairo_destroy(cr); } int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: waveflag [-debug] in.png out.png\n"); return 1; } if (!strcmp(argv[1], "-debug")) { debug = 1; argc--, argv++; } wave_flag(argv[1], argv[2]); return 0; }