blob: 6bbda618ad3067a003b4f175f116c6e395af8ba6 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// 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
//
// https://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.
// -----------------------------------------------------------------------------
//
// Image savers
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include "imageio/image_enc.h"
#include "imageio/imageio_util.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
namespace WP2 {
namespace {
void PutLE16(uint8_t* const dst, uint32_t value) {
dst[0] = (value >> 0) & 0xff;
dst[1] = (value >> 8) & 0xff;
}
void PutLE32(uint8_t* const dst, uint32_t value) {
PutLE16(dst + 0, (value >> 0) & 0xffff);
PutLE16(dst + 2, (value >> 16) & 0xffff);
}
} // namespace
#define NUM_IFD_ENTRIES 15
#define EXTRA_DATA_SIZE 16
// 10b for signature/header + n * 12b entries + 4b for IFD terminator:
#define EXTRA_DATA_OFFSET (10 + 12 * NUM_IFD_ENTRIES + 4)
#define TIFF_HEADER_SIZE (EXTRA_DATA_OFFSET + EXTRA_DATA_SIZE)
WP2Status WriteTIFF(const ArgbBuffer& buffer, FILE* fout) {
WP2_CHECK_OK(fout != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!buffer.IsEmpty(), WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(WP2Formatbpc(buffer.format()) == 8,
WP2_STATUS_INVALID_PARAMETER);
// TODO(skal): should be has_alpha = WP2ArgbBufferHasTransparency(buffer);
// but tests are failing with that.
const int has_alpha = 1;
const uint32_t width = buffer.width();
const uint32_t height = buffer.height();
ArgbBuffer converted_row(has_alpha ? WP2_rgbA_32 : WP2_RGB_24);
const uint8_t bytes_per_px = WP2FormatBpp(converted_row.format());
// For non-alpha case, we omit tag 0x152 (ExtraSamples).
const uint8_t num_ifd_entries =
has_alpha ? NUM_IFD_ENTRIES : NUM_IFD_ENTRIES - 1;
uint8_t tiff_header[TIFF_HEADER_SIZE] = {
0x49, 0x49, 0x2a, 0x00, // little endian signature
8, 0, 0, 0, // offset to the unique IFD that follows
// IFD (offset = 8). Entries must be written in increasing tag order.
num_ifd_entries, 0, // Number of entries in the IFD (12 bytes each).
0x00, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 10: Width (TBD)
0x01, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 22: Height (TBD)
0x02, 0x01, 3, 0, bytes_per_px, 0, 0, 0, // 34: BitsPerSample: 8888
EXTRA_DATA_OFFSET + 0, 0, 0, 0, //
0x03, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 46: Compression: none
0x06, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 58: Photometric: RGB
0x11, 0x01, 4, 0, 1, 0, 0, 0, // 70: Strips offset:
TIFF_HEADER_SIZE, 0, 0, 0, // data follows header
0x12, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 82: Orientation: topleft
0x15, 0x01, 3, 0, 1, 0, 0, 0, // 94: SamplesPerPixels
bytes_per_px, 0, 0, 0, //
0x16, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 106: Rows per strip (TBD)
0x17, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 118: StripByteCount (TBD)
0x1a, 0x01, 5, 0, 1, 0, 0, 0, // 130: X-resolution
EXTRA_DATA_OFFSET + 8, 0, 0, 0, //
0x1b, 0x01, 5, 0, 1, 0, 0, 0, // 142: Y-resolution
EXTRA_DATA_OFFSET + 8, 0, 0, 0, //
0x1c, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 154: PlanarConfiguration
0x28, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 166: ResolutionUnit (inch)
0x52, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 178: ExtraSamples: rgbA
0, 0, 0, 0, // 190: IFD terminator
// EXTRA_DATA_OFFSET:
8, 0, 8, 0, 8, 0, 8, 0, // BitsPerSample
72, 0, 0, 0, 1, 0, 0, 0 // 72 pixels/inch, for X/Y-resolution
};
size_t bytes_per_row = 0;
WP2_CHECK_STATUS(MultFitsIn(width, bytes_per_px, &bytes_per_row));
uint32_t strip_byte_count = 0;
WP2_CHECK_STATUS(MultFitsIn(bytes_per_row, height, &strip_byte_count));
// Fill placeholders in IFD:
PutLE32(tiff_header + 10 + 8, width);
PutLE32(tiff_header + 22 + 8, height);
PutLE32(tiff_header + 106 + 8, height);
PutLE32(tiff_header + 118 + 8, strip_byte_count);
if (!has_alpha) PutLE32(tiff_header + 178, 0); // IFD terminator
// write header
WP2_CHECK_OK(fwrite(tiff_header, sizeof(tiff_header), 1, fout) == 1,
WP2_STATUS_BAD_WRITE);
// write pixel values
for (uint32_t y = 0; y < height; ++y) {
ArgbBuffer buffer_row(buffer.format());
WP2_CHECK_STATUS(buffer_row.SetView(buffer, {0, y, buffer.width(), 1}));
if (converted_row.format() == buffer_row.format()) {
WP2_CHECK_STATUS(converted_row.SetView(buffer_row));
} else {
WP2_CHECK_STATUS(converted_row.ConvertFrom(buffer_row));
}
WP2_CHECK_OK(
fwrite(converted_row.GetRow8(0), bytes_per_px, width, fout) == width,
WP2_STATUS_BAD_WRITE);
}
return WP2_STATUS_OK;
}
#undef TIFF_HEADER_SIZE
#undef EXTRA_DATA_OFFSET
#undef EXTRA_DATA_SIZE
#undef NUM_IFD_ENTRIES
} // namespace WP2