478 lines
16 KiB
C
478 lines
16 KiB
C
/* -*- tab-width: 4; -*- */
|
|
/* vi: set sw=2 ts=4 expandtab: */
|
|
|
|
/**
|
|
* @internal
|
|
* @file writer.c
|
|
* @~English
|
|
*
|
|
* @brief Functions for creating KTX-format files from a set of images.
|
|
*
|
|
* @author Mark Callow, HI Corporation
|
|
*/
|
|
|
|
/*
|
|
* ©2018 Mark Callow.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "ktx.h"
|
|
#include "ktxint.h"
|
|
#include "stream.h"
|
|
#include "filestream.h"
|
|
#include "memstream.h"
|
|
|
|
/**
|
|
* @defgroup writer Writer
|
|
* @brief Write KTX-formatted data.
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* @internal
|
|
* @memberof ktxTexture @private
|
|
* @~English
|
|
* @brief Set image for level, layer, faceSlice from a ktxStream source.
|
|
*
|
|
* @param[in] This pointer to the target ktxTexture object.
|
|
* @param[in] level mip level of the image to set.
|
|
* @param[in] layer array layer of the image to set.
|
|
* @param[in] faceSlice cube map face or depth slice of the image to set.
|
|
* @param[in] src ktxStream pointer to the source.
|
|
* @param[in] srcSize size of the source image in bytes.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p This or @p src is NULL.
|
|
* @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
|
|
* specified level, layer & faceSlice.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* No storage was allocated when the texture was
|
|
* created.
|
|
*/
|
|
KTX_error_code
|
|
ktxTexture_setImageFromStream(ktxTexture* This, ktx_uint32_t level,
|
|
ktx_uint32_t layer, ktx_uint32_t faceSlice,
|
|
ktxStream* src, ktx_size_t srcSize)
|
|
{
|
|
ktx_uint32_t packedRowBytes, rowBytes, rowPadding, numRows;
|
|
ktx_size_t packedBytes, unpackedBytes;
|
|
ktx_size_t imageOffset;
|
|
#if (KTX_GL_UNPACK_ALIGNMENT != 4)
|
|
ktx_uint32_t faceLodPadding;
|
|
#endif
|
|
|
|
if (!This || !src)
|
|
return KTX_INVALID_VALUE;
|
|
|
|
if (!This->pData)
|
|
return KTX_INVALID_OPERATION;
|
|
|
|
ktxTexture_GetImageOffset(This, level, layer, faceSlice, &imageOffset);
|
|
|
|
if (This->isCompressed) {
|
|
packedBytes = ktxTexture_GetImageSize(This, level);
|
|
rowPadding = 0;
|
|
// These 2 are not used when rowPadding == 0. Quiets compiler warning.
|
|
packedRowBytes = 0;
|
|
rowBytes = 0;
|
|
} else {
|
|
ktxTexture_rowInfo(This, level, &numRows, &rowBytes, &rowPadding);
|
|
unpackedBytes = rowBytes * numRows;
|
|
if (rowPadding) {
|
|
packedRowBytes = rowBytes - rowPadding;
|
|
packedBytes = packedRowBytes * numRows;
|
|
} else {
|
|
packedRowBytes = rowBytes;
|
|
packedBytes = unpackedBytes;
|
|
}
|
|
}
|
|
|
|
if (srcSize != packedBytes)
|
|
return KTX_INVALID_OPERATION;
|
|
// The above will catch a flagrantly invalid srcSize. This is an
|
|
// additional check of the internal calculations.
|
|
assert (imageOffset + srcSize <= This->dataSize);
|
|
|
|
#if (KTX_GL_UNPACK_ALIGNMENT != 4)
|
|
faceLodPadding = _KTX_PAD4_LEN(faceLodSize);
|
|
#endif
|
|
|
|
if (rowPadding == 0) {
|
|
/* Can copy whole image at once */
|
|
src->read(src, This->pData + imageOffset, srcSize);
|
|
} else {
|
|
/* Copy the rows individually, padding each one */
|
|
ktx_uint32_t row;
|
|
ktx_uint8_t* dst = This->pData + imageOffset;
|
|
ktx_uint8_t pad[4] = { 0, 0, 0, 0 };
|
|
for (row = 0; row < numRows; row++) {
|
|
ktx_uint32_t rowOffset = rowBytes * row;
|
|
src->read(src, dst + rowOffset, packedRowBytes);
|
|
memcpy(dst + rowOffset + packedRowBytes, pad, rowPadding);
|
|
}
|
|
}
|
|
#if (KTX_GL_UNPACK_ALIGNMENT != 4)
|
|
/*
|
|
* When KTX_GL_UNPACK_ALIGNMENT == 4, rows, and therefore everything else,
|
|
* are always 4-byte aligned and faceLodPadding is always 0. It is always
|
|
* 0 for compressed formats too because they all have multiple-of-4 block
|
|
* sizes.
|
|
*/
|
|
if (faceLodPadding)
|
|
memcpy(This->pData + faceLodSize, pad, faceLodPadding);
|
|
#endif
|
|
return KTX_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @~English
|
|
* @brief Set image for level, layer, faceSlice from a stdio stream source.
|
|
*
|
|
* Uncompressed images read from the stream are expected to have their rows
|
|
* tightly packed as is the norm for most image file formats. The copied image
|
|
* is padded as necessary to achieve the KTX-specified row alignment. No
|
|
* padding is done if the ktxTexture's @c isCompressed field is @c KTX_TRUE.
|
|
*
|
|
* Level, layer, faceSlice rather than offset are specified to enable some
|
|
* validation.
|
|
*
|
|
* @param[in] This pointer to the target ktxTexture object.
|
|
* @param[in] level mip level of the image to set.
|
|
* @param[in] layer array layer of the image to set.
|
|
* @param[in] faceSlice cube map face or depth slice of the image to set.
|
|
* @param[in] src stdio stream pointer to the source.
|
|
* @param[in] srcSize size of the source image in bytes.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p This or @p src is NULL.
|
|
* @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
|
|
* specified level, layer & faceSlice.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* No storage was allocated when the texture was
|
|
* created.
|
|
*/
|
|
KTX_error_code
|
|
ktxTexture_SetImageFromStdioStream(ktxTexture* This, ktx_uint32_t level,
|
|
ktx_uint32_t layer, ktx_uint32_t faceSlice,
|
|
FILE* src, ktx_size_t srcSize)
|
|
{
|
|
ktxStream srcstr;
|
|
KTX_error_code result;
|
|
|
|
result = ktxFileStream_construct(&srcstr, src, KTX_FALSE);
|
|
if (result != KTX_SUCCESS)
|
|
return result;
|
|
result = ktxTexture_setImageFromStream(This, level, layer, faceSlice,
|
|
&srcstr, srcSize);
|
|
ktxFileStream_destruct(&srcstr);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @~English
|
|
* @brief Set image for level, layer, faceSlice from an image in memory.
|
|
*
|
|
* Uncompressed images in memory are expected to have their rows tightly packed
|
|
* as is the norm for most image file formats. The copied image is padded as
|
|
* necessary to achieve the KTX-specified row alignment. No padding is done if
|
|
* the ktxTexture's @c isCompressed field is @c KTX_TRUE.
|
|
*
|
|
* Level, layer, faceSlice rather than offset are specified to enable some
|
|
* validation.
|
|
*
|
|
* @warning Do not use @c memcpy for this as it will not pad when necessary.
|
|
*
|
|
* @param[in] This pointer to the target ktxTexture object.
|
|
* @param[in] level mip level of the image to set.
|
|
* @param[in] layer array layer of the image to set.
|
|
* @param[in] faceSlice cube map face or depth slice of the image to set.
|
|
* @param[in] src pointer to the image source in memory.
|
|
* @param[in] srcSize size of the source image in bytes.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p This or @p src is NULL.
|
|
* @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
|
|
* specified level, layer & faceSlice.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* No storage was allocated when the texture was
|
|
* created.
|
|
*/
|
|
KTX_error_code
|
|
ktxTexture_SetImageFromMemory(ktxTexture* This, ktx_uint32_t level,
|
|
ktx_uint32_t layer, ktx_uint32_t faceSlice,
|
|
const ktx_uint8_t* src, ktx_size_t srcSize)
|
|
{
|
|
ktxStream srcstr;
|
|
KTX_error_code result;
|
|
|
|
result = ktxMemStream_construct_ro(&srcstr, src, srcSize);
|
|
if (result != KTX_SUCCESS)
|
|
return result;
|
|
result = ktxTexture_setImageFromStream(This, level, layer, faceSlice,
|
|
&srcstr, srcSize);
|
|
ktxMemStream_destruct(&srcstr);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
* @memberof ktxTexture @private
|
|
* @~English
|
|
* @brief Write a ktxTexture object to a ktxStream in KTX format.
|
|
*
|
|
* @param[in] This pointer to the target ktxTexture object.
|
|
* @param[in] dststr destination ktxStream.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p This or @p dststr is NULL.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The ktxTexture does not contain any image data.
|
|
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
|
* the system.
|
|
* @exception KTX_FILE_WRITE_ERROR
|
|
* An error occurred while writing the file.
|
|
*/
|
|
static KTX_error_code
|
|
ktxTexture_writeToStream(ktxTexture* This, ktxStream* dststr)
|
|
{
|
|
KTX_header header = KTX_IDENTIFIER_REF;
|
|
KTX_error_code result = KTX_SUCCESS;
|
|
ktx_uint32_t kvdLen;
|
|
ktx_uint8_t* pKvd;
|
|
ktx_uint32_t level, levelOffset;
|
|
|
|
if (!dststr) {
|
|
return KTX_INVALID_VALUE;
|
|
}
|
|
|
|
if (This->pData == NULL)
|
|
return KTX_INVALID_OPERATION;
|
|
|
|
//endianess int.. if this comes out reversed, all of the other ints will too.
|
|
header.endianness = KTX_ENDIAN_REF;
|
|
header.glInternalformat = This->glInternalformat;
|
|
header.glFormat = This->glFormat;
|
|
header.glBaseInternalformat = This->glBaseInternalformat;
|
|
header.glType = This->glType;
|
|
header.glTypeSize = ktxTexture_glTypeSize(This);
|
|
header.pixelWidth = This->baseWidth;
|
|
header.pixelHeight = This->baseHeight;
|
|
header.pixelDepth = This->baseDepth;
|
|
header.numberOfArrayElements = This->isArray ? This->numLayers : 0;
|
|
assert (This->isCubemap ? This->numFaces == 6 : This->numFaces == 1);
|
|
header.numberOfFaces = This->numFaces;
|
|
assert (This->generateMipmaps ? This->numLevels == 1 : This->numLevels >= 1);
|
|
header.numberOfMipmapLevels = This->generateMipmaps ? 0 : This->numLevels;
|
|
|
|
ktxHashList_Serialize(&This->kvDataHead, &kvdLen, &pKvd);
|
|
header.bytesOfKeyValueData = kvdLen;
|
|
|
|
//write header
|
|
result = dststr->write(dststr, &header, sizeof(KTX_header), 1);
|
|
if (result != KTX_SUCCESS)
|
|
return result;
|
|
|
|
//write keyValueData
|
|
if (kvdLen != 0) {
|
|
assert(pKvd != NULL);
|
|
|
|
result = dststr->write(dststr, pKvd, 1, kvdLen);
|
|
free(pKvd);
|
|
if (result != KTX_SUCCESS)
|
|
return result;
|
|
}
|
|
|
|
/* Write the image data */
|
|
for (level = 0, levelOffset=0; level < This->numLevels; ++level)
|
|
{
|
|
ktx_uint32_t faceLodSize, layer, levelDepth, numImages;
|
|
ktx_size_t imageSize;
|
|
|
|
faceLodSize = (ktx_uint32_t)ktxTexture_faceLodSize(This, level);
|
|
imageSize = ktxTexture_GetImageSize(This, level);
|
|
levelDepth = MAX(1, This->baseDepth >> level);
|
|
if (This->isCubemap && !This->isArray)
|
|
numImages = This->numFaces;
|
|
else
|
|
numImages = This->isCubemap ? This->numFaces : levelDepth;
|
|
|
|
result = dststr->write(dststr, &faceLodSize, sizeof(faceLodSize), 1);
|
|
if (result != KTX_SUCCESS)
|
|
goto cleanup;
|
|
|
|
for (layer = 0; layer < This->numLayers; layer++) {
|
|
ktx_uint32_t faceSlice;
|
|
|
|
for (faceSlice = 0; faceSlice < numImages; faceSlice++) {
|
|
result = dststr->write(dststr, This->pData + levelOffset,
|
|
imageSize, 1);
|
|
levelOffset += (ktx_uint32_t)imageSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @~English
|
|
* @brief Write a ktxTexture object to a stdio stream in KTX format.
|
|
*
|
|
* @param[in] This pointer to the target ktxTexture object.
|
|
* @param[in] dstsstr destination stdio stream.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p This or @p dstsstr is NULL.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The ktxTexture does not contain any image data.
|
|
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
|
* the system.
|
|
* @exception KTX_FILE_WRITE_ERROR
|
|
* An error occurred while writing the file.
|
|
*/
|
|
KTX_error_code
|
|
ktxTexture_WriteToStdioStream(ktxTexture* This, FILE* dstsstr)
|
|
{
|
|
ktxStream stream;
|
|
KTX_error_code result = KTX_SUCCESS;
|
|
|
|
if (!This)
|
|
return KTX_INVALID_VALUE;
|
|
|
|
result = ktxFileStream_construct(&stream, dstsstr, KTX_FALSE);
|
|
if (result != KTX_SUCCESS)
|
|
return result;
|
|
|
|
return ktxTexture_writeToStream(This, &stream);
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @~English
|
|
* @brief Write a ktxTexture object to a named file in KTX format.
|
|
*
|
|
* @param[in] This pointer to the target ktxTexture object.
|
|
* @param[in] dstname destination file name.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p This or @p dstname is NULL.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The ktxTexture does not contain any image data.
|
|
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
|
* the system.
|
|
* @exception KTX_FILE_WRITE_ERROR
|
|
* An error occurred while writing the file.
|
|
*/
|
|
KTX_error_code
|
|
ktxTexture_WriteToNamedFile(ktxTexture* This, const char* const dstname)
|
|
{
|
|
KTX_error_code result;
|
|
FILE* dst;
|
|
|
|
if (!This)
|
|
return KTX_INVALID_VALUE;
|
|
|
|
dst = fopen(dstname, "wb");
|
|
if (dst) {
|
|
result = ktxTexture_WriteToStdioStream(This, dst);
|
|
fclose(dst);
|
|
} else
|
|
result = KTX_FILE_OPEN_FAILED;
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @memberof ktxTexture
|
|
* @~English
|
|
* @brief Write a ktxTexture object to block of memory in KTX format.
|
|
*
|
|
* Memory is allocated by the function and the caller is responsible for
|
|
* freeing it.
|
|
*
|
|
* @param[in] This pointer to the target ktxTexture object.
|
|
* @param[in,out] ppDstBytes pointer to location to write the address of
|
|
* the destination memory. The Application is
|
|
* responsible for freeing this memory.
|
|
* @param[in,out] pSize pointer to location to write the size in bytes of
|
|
* the KTX data.
|
|
*
|
|
* @return KTX_SUCCESS on success, other KTX_* enum values on error.
|
|
*
|
|
* @exception KTX_INVALID_VALUE @p This, @p ppDstBytes or @p pSize is NULL.
|
|
* @exception KTX_INVALID_OPERATION
|
|
* The ktxTexture does not contain any image data.
|
|
* @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
|
|
* the system.
|
|
* @exception KTX_FILE_WRITE_ERROR
|
|
* An error occurred while writing the file.
|
|
*/
|
|
KTX_error_code
|
|
ktxTexture_WriteToMemory(ktxTexture* This,
|
|
ktx_uint8_t** ppDstBytes, ktx_size_t* pSize)
|
|
{
|
|
struct ktxStream dststr;
|
|
KTX_error_code result;
|
|
ktx_size_t strSize;
|
|
|
|
if (!This || !ppDstBytes || !pSize)
|
|
return KTX_INVALID_VALUE;
|
|
|
|
*ppDstBytes = NULL;
|
|
|
|
result = ktxMemStream_construct(&dststr, KTX_FALSE);
|
|
if (result != KTX_SUCCESS)
|
|
return result;
|
|
|
|
result = ktxTexture_writeToStream(This, &dststr);
|
|
if(result != KTX_SUCCESS)
|
|
{
|
|
ktxMemStream_destruct(&dststr);
|
|
return result;
|
|
}
|
|
|
|
ktxMemStream_getdata(&dststr, ppDstBytes);
|
|
dststr.getsize(&dststr, &strSize);
|
|
*pSize = (GLsizei)strSize;
|
|
/* This function does not free the memory pointed at by the
|
|
* value obtained from ktxMemStream_getdata() thanks to the
|
|
* KTX_FALSE passed to the constructor above.
|
|
*/
|
|
ktxMemStream_destruct(&dststr);
|
|
return KTX_SUCCESS;
|
|
|
|
}
|
|
|
|
/** @} */
|
|
|