Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to generate bitmap tiles as JPEG instead of PNG #33

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ tile-count-decode: tippecanoe/projection.o decode.o header.o serial.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread

tile-count-tile: tippecanoe/projection.o tile.o header.o serial.o tippecanoe/mbtiles.o tippecanoe/mvt.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread -lpng
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread -lpng -ljpeg

tile-count-merge: mergetool.o header.o serial.o merge.o
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ instead of merging existing tilesets. The *maxzoom* plus the *detail* always equ

* `-b`: Create PNG raster tiles instead of vectors. If you are not planning to use these tiles with Mapbox GL,
you will probably also want to specify `-d8` for normal 256x256 web map tile resolution.
* `-j`: Create JPEG raster tiles instead of vectors.
* `-c` *rrggbb*: Specify the color to use in raster tiles as a hex color.
* `-w`: Make tiles for a white background instead of a black background.

Expand Down
201 changes: 160 additions & 41 deletions tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <math.h>
#include <time.h>
#include <png.h>
#include <jpeglib.h>
#include "tippecanoe/projection.hpp"
#include "protozero/varint.hpp"
#include "protozero/pbf_reader.hpp"
Expand All @@ -32,6 +33,7 @@ int first_count = 0;
double count_gamma = 2.5;

bool bitmap = false;
bool jpeg = false;
int color = 0x888888;
int white = 0;

Expand Down Expand Up @@ -168,6 +170,79 @@ static void fail(png_structp png_ptr, png_const_charp error_msg) {
exit(EXIT_FAILURE);
}

void get_color(int i, png_color &out, png_byte &transparency) {
if (i < levels / 2) {
out.red = (color >> 16) & 0xFF;
out.green = (color >> 8) & 0xFF;
out.blue = (color >> 0) & 0xFF;
transparency = 255 * i / (levels / 2);
} else {
double along = 255 * (i - levels / 2) / (levels - levels / 2 - 1) / 255.0;
int fg = white ? 0x00 : 0xFF;

out.red = ((color >> 16) & 0xFF) * (1 - along) + fg * (along);
out.green = ((color >> 8) & 0xFF) * (1 - along) + fg * (along);
out.blue = ((color >> 0) & 0xFF) * (1 - along) + fg * (along);
transparency = 255;
}
}

struct jpeg_writer {
struct jpeg_destination_mgr pub;

unsigned char *buffer;
size_t capacity;

std::string *s;

jpeg_writer(std::string *out) {
capacity = 4000;
buffer = new unsigned char[capacity];
s = out;
}

~jpeg_writer() {
delete[] buffer;
}
};

void jpeg_writer_init_destination(j_compress_ptr cinfo) {
jpeg_writer *dest = (jpeg_writer *) cinfo->dest;

*(dest->s) = "";
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = dest->capacity;
}

boolean jpeg_writer_empty_output_buffer(j_compress_ptr cinfo) {
jpeg_writer *dest = (jpeg_writer *) cinfo->dest;

*(dest->s) += std::string((char *) dest->buffer, dest->capacity);
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = dest->capacity;
return TRUE;
}

void jpeg_writer_term_destination(j_compress_ptr cinfo) {
jpeg_writer *dest = (jpeg_writer *) cinfo->dest;

size_t datacount = dest->capacity - dest->pub.free_in_buffer;
if (datacount > 0) {
*(dest->s) += std::string((char *) dest->buffer, datacount);
}
}

jpeg_writer *jpeg_writer_dest(j_compress_ptr cinfo, std::string *out) {
cinfo->dest = (struct jpeg_destination_mgr *) new jpeg_writer(out);

jpeg_writer *dest = (jpeg_writer *) cinfo->dest;
dest->pub.init_destination = jpeg_writer_init_destination;
dest->pub.empty_output_buffer = jpeg_writer_empty_output_buffer;
dest->pub.term_destination = jpeg_writer_term_destination;

return dest;
}

void make_tile(sqlite3 *outdb, tile &tile, int z, int detail, long long zoom_max, std::string const &layername) {
std::string compressed;

Expand Down Expand Up @@ -203,7 +278,7 @@ void make_tile(sqlite3 *outdb, tile &tile, int z, int detail, long long zoom_max

if (bitmap) {
bool anything = false;
for (size_t y = 0; y < 1U << detail; y++) {
for (size_t y = 0; y < (1U << detail); y++) {
for (size_t x = 0; x < (1U << detail); x++) {
long long density = normalized[y * (1 << detail) + x];
if (density > 0) {
Expand All @@ -225,48 +300,87 @@ void make_tile(sqlite3 *outdb, tile &tile, int z, int detail, long long zoom_max
}
}

png_structp png_ptr;
png_infop info_ptr;
if (jpeg) {
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1];

png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, fail, fail);
if (png_ptr == NULL) {
fprintf(stderr, "PNG failure (write struct)\n");
exit(EXIT_FAILURE);
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_write_struct(&png_ptr, NULL);
fprintf(stderr, "PNG failure (info struct)\n");
exit(EXIT_FAILURE);
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_writer *jw = jpeg_writer_dest(&cinfo, &compressed);

cinfo.image_width = 1U << detail;
cinfo.image_height = 1U << detail;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, 75, TRUE);
jpeg_start_compress(&cinfo, TRUE);

for (size_t y = 0; y < (1U << detail); y++) {
unsigned char buf[3U << detail];
int base = 0;

if (white) {
base = 255;
} else {
base = 0;
}

for (size_t x = 0; x < (1U << detail); x++) {
png_byte transparency;
png_color c;

get_color(rows[y][x], c, transparency);

c.red = c.red * transparency / 255.0 + base * (255 - transparency) / 255.0;
c.green = c.green * transparency / 255.0 + base * (255 - transparency) / 255.0;
c.blue = c.blue * transparency / 255.0 + base * (255 - transparency) / 255.0;

buf[3 * x + 0] = c.red;
buf[3 * x + 1] = c.green;
buf[3 * x + 2] = c.blue;
}

row_pointer[0] = buf;
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}

jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
delete jw;
} else {
png_set_IHDR(png_ptr, info_ptr, 1U << detail, 1U << detail, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

png_byte transparency[levels];
png_color colors[levels];
for (int i = 0; i < levels / 2; i++) {
colors[i].red = (color >> 16) & 0xFF;
colors[i].green = (color >> 8) & 0xFF;
colors[i].blue = (color >> 0) & 0xFF;
transparency[i] = 255 * i / (levels / 2);
png_structp png_ptr;
png_infop info_ptr;

png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, fail, fail);
if (png_ptr == NULL) {
fprintf(stderr, "PNG failure (write struct)\n");
exit(EXIT_FAILURE);
}
for (int i = levels / 2; i < levels; i++) {
double along = 255 * (i - levels / 2) / (levels - levels / 2 - 1) / 255.0;
int fg = white ? 0x00 : 0xFF;

colors[i].red = ((color >> 16) & 0xFF) * (1 - along) + fg * (along);
colors[i].green = ((color >> 8) & 0xFF) * (1 - along) + fg * (along);
colors[i].blue = ((color >> 0) & 0xFF) * (1 - along) + fg * (along);
transparency[i] = 255;
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_write_struct(&png_ptr, NULL);
fprintf(stderr, "PNG failure (info struct)\n");
exit(EXIT_FAILURE);
} else {
png_set_IHDR(png_ptr, info_ptr, 1U << detail, 1U << detail, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

png_byte transparency[levels];
png_color colors[levels];
for (int i = 0; i < levels; i++) {
get_color(i, colors[i], transparency[i]);
}
png_set_tRNS(png_ptr, info_ptr, transparency, levels, NULL);
png_set_PLTE(png_ptr, info_ptr, colors, levels);

png_set_rows(png_ptr, info_ptr, rows);
png_set_write_fn(png_ptr, &compressed, string_append, NULL);
png_write_png(png_ptr, info_ptr, 0, NULL);
png_write_end(png_ptr, info_ptr);
png_destroy_info_struct(png_ptr, &info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
}
png_set_tRNS(png_ptr, info_ptr, transparency, levels, NULL);
png_set_PLTE(png_ptr, info_ptr, colors, levels);

png_set_rows(png_ptr, info_ptr, rows);
png_set_write_fn(png_ptr, &compressed, string_append, NULL);
png_write_png(png_ptr, info_ptr, 0, NULL);
png_write_end(png_ptr, info_ptr);
png_destroy_info_struct(png_ptr, &info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
}

for (size_t i = 0; i < 1U << detail; i++) {
Expand Down Expand Up @@ -1161,7 +1275,7 @@ int main(int argc, char **argv) {
std::string layername = "count";

int i;
while ((i = getopt(argc, argv, "fz:Z:s:a:o:p:d:l:m:M:g:bwc:qn:y:1k")) != -1) {
while ((i = getopt(argc, argv, "fz:Z:s:a:o:p:d:l:m:M:g:bjwc:qn:y:1k")) != -1) {
switch (i) {
case 'f':
force = true;
Expand Down Expand Up @@ -1235,6 +1349,11 @@ int main(int argc, char **argv) {
bitmap = 1;
break;

case 'j':
bitmap = 1;
jpeg = 1;
break;

case 'c':
color = strtoul(optarg, NULL, 16);
break;
Expand Down Expand Up @@ -1502,7 +1621,7 @@ int main(int argc, char **argv) {
lm.insert(std::pair<std::string, layermap_entry>(layername, lme));
}

mbtiles_write_metadata(outdb, outfile, 0, zooms - 1, minlat, minlon, maxlat, maxlon, midlat, midlon, false, "", lm, !bitmap);
mbtiles_write_metadata(outdb, outfile, 0, zooms - 1, minlat, minlon, maxlat, maxlon, midlat, midlon, false, "", lm, jpeg ? "jpg" : bitmap ? "png" : "pbf");

write_meta(zoom_max, outdb);

Expand Down
6 changes: 3 additions & 3 deletions tippecanoe/mbtiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ bool type_and_string::operator<(const type_and_string &o) const {
return false;
}

void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector) {
void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, const char *type) {
char *sql, *err;

sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('name', %Q);", fname);
Expand Down Expand Up @@ -217,7 +217,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
sqlite3_free(sql);
}

sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('format', %Q);", vector ? "pbf" : "png");
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('format', %Q);", type);
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set format: %s\n", err);
if (!forcetable) {
Expand All @@ -226,7 +226,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
}
sqlite3_free(sql);

if (vector) {
if (strcmp(type, "pbf") == 0) {
std::string buf("{");
aprintf(&buf, "\"vector_layers\": [ ");

Expand Down
2 changes: 1 addition & 1 deletion tippecanoe/mbtiles.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ sqlite3 *mbtiles_open(char *dbname, char **argv, int forcetable);

void mbtiles_write_tile(sqlite3 *outdb, int z, int tx, int ty, const char *data, int size);

void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector);
void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, const char *type);

void mbtiles_close(sqlite3 *outdb, char **argv);

Expand Down