xlnt/samples/img2xlsx.cpp

243 lines
8.4 KiB
C++

// Copyright (c) 2017-2018 Thomas Fussell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE
//
// @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file
#include <array>
#include <iostream>
#include <unordered_set>
#include <vector>
#include <xlnt/xlnt.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
namespace {
// This sample demonstrates the use of some complex formatting methods to create
// a workbook in which each cell has a fill based on the pixels of an image
// thereby appearing as a mosaic of the given image. Two methods for achieving
// this effect are demonstrated: cell-level format and conditional formatting.
// Clean up the code with some alias declarations
using byte = std::uint8_t;
using pixel = std::array<byte, 3>;
using pixmap = std::vector<std::vector<pixel>>;
// Specify whether the CF or cell format method should be used
static const bool use_conditional_formatting = false;
// Returns a 2D vector of pixels from a given filename. Accepts all file types
// supported by stb_image (JPEG, PNG, TGA, BMP, PSD, GIF, HDR, PIC, PNM).
pixmap load_image(const std::string &filename)
{
int width = 0;
int height = 0;
int bpp = 0;
// Must be freed after use with stbi_image_free
auto image_data = stbi_load(filename.c_str(), &width, &height, &bpp, 0);
if (image_data == nullptr)
{
throw xlnt::exception("bad image or file not found: " + filename);
}
pixmap result;
for (auto row = 0; row < height; ++row)
{
std::vector<pixel> row_pixels;
for (auto column = 0; column < width; ++column)
{
auto r = image_data[row * width * bpp + column * bpp];
auto g = image_data[row * width * bpp + column * bpp + 1];
auto b = image_data[row * width * bpp + column * bpp + 2];
auto current_pixel = pixel({ r, g, b });
row_pixels.push_back(current_pixel);
}
result.push_back(row_pixels);
}
stbi_image_free(image_data);
return result;
}
// Builds and returns a workbook in which each cell has a value
// equal to the color of the pixel in the given image. A conditional format
// is created for every color to set the background fill of each cell to the
// color of its value. This is very slow for large images but is intended
// to illustrate a realistic use of conditional formatting.
xlnt::workbook build_workbook_cf(const pixmap &image)
{
// Create a default workbook with a single worksheet
xlnt::workbook wb;
// Get the active sheet (the only sheet in this case)
auto ws = wb.active_sheet();
// The reference to the cell which is being operated upon
auto current_cell = xlnt::cell_reference("A1");
// The range of cells which will be modified. This is required for conditional formats
auto range = ws.range(xlnt::range_reference(1, 1,
static_cast<xlnt::column_t::index_t>(image[0].size()),
static_cast<xlnt::row_t>(image.size())));
// Track the previously created conditonal formats so they are only created once
std::unordered_set<std::string> defined_colors;
// Iterate over each row in the source image
for (const auto &image_row : image)
{
// Iterate over each pixel in the image row
for (const auto &image_pixel : image_row)
{
// Build an xlnt compatible RGB color from the pixel byte array
const auto color = xlnt::rgb_color(image_pixel[0], image_pixel[1], image_pixel[2]);
// Only create the conditional format if it doesn't yet exist
if (defined_colors.count(color.hex_string()) == 0)
{
// The condition under which the conditional format applies to a cell
// In this case, the condition is satisfied when the text of the cell
// contains the hex string representing the pixel color.
const auto condition = xlnt::condition::text_contains(color.hex_string());
// Create a new conditional format with the above condition on the image pixel range
auto format = range.conditional_format(condition);
// Define the fill for the conditional format
format.fill(xlnt::pattern_fill().background(color));
// Record the created of this CF
defined_colors.insert(color.hex_string());
}
// Dereference the cell at the current position and set its value to the pixel color
ws.cell(current_cell).value(color.hex_string());
// Increment the column
current_cell.column_index(current_cell.column_index() + 1);
}
// Reached the end of the row--move to the first column of the next row
current_cell.row(current_cell.row() + 1);
current_cell.column_index("A");
// Show some progress, it can take a while...
std::cout << current_cell.row() << " " << defined_colors.size() << std::endl;
}
// Return the resulting workbook
return wb;
}
// Builds and returns a workbook in which each cell has a value
// equal to the color of the pixel in the given image. To accomplish this,
// a named style is created for every color in the image and is applied
// to each cell corresponding to pixels of that color.
xlnt::workbook build_workbook_normal(const pixmap &image)
{
// Create a default workbook with a single worksheet
xlnt::workbook wb;
// Get the active sheet (the only sheet in this case)
auto ws = wb.active_sheet();
// The reference to the cell which is being operated upon
auto current_cell = xlnt::cell_reference("A1");
// Iterate over each row in the source image
for (const auto &image_row : image)
{
// Iterate over each pixel in the image row
for (const auto &image_pixel : image_row)
{
// Build an xlnt compatible RGB color from the pixel byte array
const auto color = xlnt::rgb_color(image_pixel[0], image_pixel[1], image_pixel[2]);
// Only create the style if it doesn't yet exist in the workbook
if (!wb.has_style(color.hex_string()))
{
// A style is constructed on a workbook by providing a unique name
auto color_style = wb.create_style(color.hex_string());
// Set the fill to a solid fill of the pixel color
color_style.fill(xlnt::fill::solid(color));
}
// Dereference the cell at the current position and use the previously created color style
ws.cell(current_cell).style(color.hex_string());
// Increment the column
current_cell.column_index(current_cell.column_index() + 1);
}
// Reached the end of the row--move to the first column of the next row
current_cell.row(current_cell.row() + 1);
current_cell.column_index("A");
// Show some progress, it can take a while...
std::cout << current_cell.row() << std::endl;
}
// Return the resulting workbook
return wb;
}
// Builds and returns a workbook in which each cell has a value
// equal to the color of the pixel in the given image using the
// conditional formatting or cell-level formatting depending on
// the value of use_conditional_formatting defined at the top of
// this file.
xlnt::workbook build_workbook(const pixmap &image)
{
if (use_conditional_formatting)
{
return build_workbook_cf(image);
}
else
{
return build_workbook_normal(image);
}
}
} // namespace
// Entry point
int main(int argc, char *argv[])
{
// Ensure that there is a correct number of arguments
if (argc < 3)
{
std::cout << "usage: img2xlsx <input> <output>" << std::endl;
return 0;
}
// The first argument is the name of the input image
const auto input = std::string(argv[1]);
// Load the input image. An exception will be thrown if it doesn't exist.
const auto image = load_image(input);
// Build the workbook from the image
auto workbook = build_workbook(image);
// The second argument is the name of the file to save the workbook as.
const auto output = std::string(argv[2]);
// Save the workbook
workbook.save(output);
return 0;
}