From a2ad3b9d4b5a77584be04c67d90f7a2607122ef0 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 23 Dec 2016 13:06:24 +0100 Subject: [PATCH] gmio_amf: add ZIP archiving (Zip64 format to be further supported) --- src/gmio_amf/amf_document.h | 34 ++- src/gmio_amf/amf_io.c | 338 ++++++++++++++++------------ src/gmio_amf/amf_io_options.h | 4 +- src/gmio_core/global.h | 5 + src/gmio_core/internal/zlib_utils.c | 43 ++-- tests/test_amf_io.c | 79 ++++++- 6 files changed, 326 insertions(+), 177 deletions(-) diff --git a/src/gmio_amf/amf_document.h b/src/gmio_amf/amf_document.h index a2158b4..d08116e 100644 --- a/src/gmio_amf/amf_document.h +++ b/src/gmio_amf/amf_document.h @@ -47,16 +47,16 @@ struct gmio_amf_metadata { - const char* type; /* UTF8-encoded */ - const char* data; /* UTF8-encoded */ + const char* type; /*!< UTF8-encoded */ + const char* data; /*!< UTF8-encoded */ }; struct gmio_amf_color { - double r; /* in [0,1] */ - double g; /* in [0,1] */ - double b; /* in [0,1] */ - double a; /* in [0,1] optional */ + double r; /*!< Red channel in [0,1] */ + double g; /*!< Green channel in [0,1] */ + double b; /*!< Blue channel in [0,1] */ + double a; /*!< Optional alpha(transparency) channel in [0,1] */ const char* r_formula; const char* g_formula; const char* b_formula; @@ -71,13 +71,19 @@ struct gmio_amf_material uint32_t metadata_count; }; +/*! Proportion of the composition of another material + * + * The proportion can be specified as a formula(with \c value_formula) or as a + * constant mixing(with \c value). + */ struct gmio_amf_composite { uint32_t materialid; /* XML:nonNegativeInteger, required */ - double value; /* governs the percent of material */ + double value; /*!< governs the percent of material */ const char* value_formula; }; +/*! Vertex within an AMF mesh */ struct gmio_amf_vertex { struct gmio_vec3d coords; @@ -88,6 +94,7 @@ struct gmio_amf_vertex uint32_t metadata_count; }; +/*! Edge within an AMF mesh, for curved triangles */ struct gmio_amf_edge { uint32_t v1; /* XML:nonNegativeInteger */ @@ -126,11 +133,12 @@ enum gmio_amf_volume_type GMIO_AMF_VOLUME_TYPE_SUPPORT }; +/*! Volume within an AMF mesh */ struct gmio_amf_volume { uint32_t materialid; /* XML:nonNegativeInteger */ enum gmio_amf_volume_type type; - uint32_t triangle_count; /* Should be >= 4 */ + uint32_t triangle_count; /*!< Should be >= 4 */ uint32_t metadata_count; bool has_color; struct gmio_amf_color color; /* XML:Color */ @@ -155,10 +163,11 @@ struct gmio_amf_object struct gmio_amf_constellation { uint32_t id; /* XML:integer */ - uint32_t instance_count; /* Should be >= 2 */ + uint32_t instance_count; /*!< Should be >= 2 */ uint32_t metadata_count; }; +/*! Instance within an AMF constellation */ struct gmio_amf_instance { uint32_t objectid; /* XML:nonNegativeInteger */ @@ -179,9 +188,10 @@ struct gmio_amf_texture uint32_t depth; /* XML:nonNegativeInteger */ bool tiled; enum gmio_amf_texture_type type; - struct gmio_memblock binary_data; /* Will be converted to base64 */ + struct gmio_memblock binary_data; /*!< Will be converted to base64 */ }; +/*! Units supported by AMF */ enum gmio_amf_unit { GMIO_AMF_UNIT_UNKNOWN, @@ -192,6 +202,7 @@ enum gmio_amf_unit GMIO_AMF_UNIT_MICRON }; +/*! The direct elements of an AMF document(ie. inside ...) */ enum gmio_amf_document_element { GMIO_AMF_DOCUMENT_ELEMENT_OBJECT, @@ -201,6 +212,7 @@ enum gmio_amf_document_element GMIO_AMF_DOCUMENT_ELEMENT_METADATA }; +/*! The direct elements of an AMF mesh(ie. inside ...) */ enum gmio_amf_mesh_element { GMIO_AMF_MESH_ELEMENT_VERTEX, @@ -223,7 +235,7 @@ struct gmio_amf_document const void* cookie; enum gmio_amf_unit unit; - uint32_t object_count; /* Must be >= 1 */ + uint32_t object_count; /*!< Must be >= 1 */ uint32_t material_count; uint32_t texture_count; uint32_t constellation_count; diff --git a/src/gmio_amf/amf_io.c b/src/gmio_amf/amf_io.c index d3e7531..d3f746b 100644 --- a/src/gmio_amf/amf_io.c +++ b/src/gmio_amf/amf_io.c @@ -37,6 +37,7 @@ #include "../gmio_core/internal/helper_stream.h" #include "../gmio_core/internal/helper_task_iface.h" #include "../gmio_core/internal/ostringstream.h" +#include "../gmio_core/internal/zip_utils.h" #include "../gmio_core/internal/zlib_utils.h" #include @@ -58,8 +59,12 @@ struct gmio_amf_wcontext struct gmio_memblock z_memblock; struct z_stream_s z_stream; int z_flush; + uintmax_t z_compressed_size; + uintmax_t z_uncompressed_size; + uint32_t z_crc32; }; +/* Helper to set error code of the writing context */ GMIO_INLINE bool gmio_amf_wcontext_set_error( struct gmio_amf_wcontext* context, int error) { @@ -67,6 +72,7 @@ GMIO_INLINE bool gmio_amf_wcontext_set_error( return gmio_no_error(error); } +/* Helper to increment the current task progress of the writing context */ GMIO_INLINE void gmio_amf_wcontext_incr_task_progress( struct gmio_amf_wcontext* context) { @@ -163,13 +169,11 @@ static void gmio_amf_write_amf_begin( } /* Writes document metadata to stream */ -static bool gmio_amf_write_root_metadata( - struct gmio_amf_wcontext* context) +static bool gmio_amf_write_root_metadata(struct gmio_amf_wcontext* context) { const struct gmio_amf_document* doc = context->document; struct gmio_amf_metadata metadata = {0}; - uint32_t imeta; - for (imeta = 0; imeta < doc->metadata_count; ++imeta) { + for (uint32_t imeta = 0; imeta < doc->metadata_count; ++imeta) { doc->func_get_document_element( doc->cookie, GMIO_AMF_DOCUMENT_ELEMENT_METADATA, imeta, &metadata); @@ -180,31 +184,27 @@ static bool gmio_amf_write_root_metadata( } /* Writes document materials to stream */ -static bool gmio_amf_write_root_materials( - struct gmio_amf_wcontext* context) +static bool gmio_amf_write_root_materials(struct gmio_amf_wcontext* context) { const struct gmio_amf_document* doc = context->document; struct gmio_ostringstream* sstream = context->sstream; struct gmio_amf_material material = {0}; - uint32_t imat; - for (imat = 0; imat < doc->material_count; ++imat) { + for (uint32_t imat = 0; imat < doc->material_count; ++imat) { doc->func_get_document_element( doc->cookie, GMIO_AMF_DOCUMENT_ELEMENT_MATERIAL, imat, &material); gmio_ostringstream_write_chararray(sstream, "'); + gmio_ostringstream_write_chararray(sstream, ">\n"); /* Write material elements */ if (material.metadata_count > 0) { - uint32_t imeta; - struct gmio_amf_metadata metadata = {0}; - /* Check function pointer is defined */ if (doc->func_get_document_element_metadata == NULL) { return gmio_amf_wcontext_set_error( context, GMIO_AMF_ERROR_NULL_FUNC_GET_DOCUMENT_ELEMENT_METADATA); } - for (imeta = 0; imeta < material.metadata_count; ++imeta) { + struct gmio_amf_metadata metadata = {0}; + for (uint32_t imeta = 0; imeta < material.metadata_count; ++imeta) { doc->func_get_document_element_metadata( doc->cookie, GMIO_AMF_DOCUMENT_ELEMENT_MATERIAL, @@ -218,15 +218,13 @@ static bool gmio_amf_write_root_materials( gmio_amf_write_color(context, &material.color); /* Write material elements */ if (material.composite_count > 0) { - struct gmio_amf_composite composite = {0}; - uint32_t icomp; - /* Check function pointer is defined */ if (doc->func_get_material_composite == NULL) { return gmio_amf_wcontext_set_error( context, GMIO_AMF_ERROR_NULL_FUNC_GET_MATERIAL_COMPOSITE); } - for (icomp = 0; icomp < material.composite_count; ++icomp) { + struct gmio_amf_composite composite = {0}; + for (uint32_t icomp = 0; icomp < material.composite_count; ++icomp) { doc->func_get_material_composite( doc->cookie, imat, icomp, &composite); gmio_ostringstream_write_chararray(sstream, "f64_format; - struct gmio_amf_vertex vertex = {0}; - uint32_t ivert; /* Write mesh element */ - gmio_ostringstream_write_chararray(sstream, "\n"); - for (ivert = 0; ivert < mesh->vertex_count; ++ivert) { + struct gmio_amf_vertex vertex = {0}; + gmio_ostringstream_write_chararray(sstream, "\n\n"); + for (uint32_t ivert = 0; ivert < mesh->vertex_count; ++ivert) { mesh_elt_index.value = ivert; doc->func_get_object_mesh_element( doc->cookie, @@ -320,15 +317,13 @@ static bool gmio_amf_write_mesh( } /* Write elements */ if (vertex.metadata_count > 0) { - struct gmio_amf_metadata metadata = {0}; - uint32_t imeta; - /* Check function pointer */ if (doc->func_get_object_mesh_vertex_metadata == NULL) { return gmio_amf_wcontext_set_error( context, GMIO_AMF_ERROR_NULL_FUNC_GET_OBJECT_MESH_VERTEX_METADATA); } - for (imeta = 0; imeta < vertex.metadata_count; ++imeta) { + struct gmio_amf_metadata metadata = {0}; + for (uint32_t imeta = 0; imeta < vertex.metadata_count; ++imeta) { doc->func_get_object_mesh_vertex_metadata( doc->cookie, &mesh_elt_index, imeta, &metadata); gmio_amf_write_metadata(sstream, &metadata); @@ -342,8 +337,7 @@ static bool gmio_amf_write_mesh( /* Write mesh vertices elements */ if (mesh->edge_count > 0) { struct gmio_amf_edge edge = {0}; - uint32_t iedge; - for (iedge = 0; iedge < mesh->edge_count; ++iedge) { + for (uint32_t iedge = 0; iedge < mesh->edge_count; ++iedge) { mesh_elt_index.value = iedge; doc->func_get_object_mesh_element( doc->cookie, @@ -374,9 +368,7 @@ static bool gmio_amf_write_mesh( /* Write mesh elements */ if (mesh->volume_count > 0) { struct gmio_amf_volume volume = {0}; - uint32_t ivol; - for (ivol = 0; ivol < mesh->volume_count; ++ivol) { - const char* str_volume_type = ""; + for (uint32_t ivol = 0; ivol < mesh->volume_count; ++ivol) { mesh_elt_index.value = ivol; doc->func_get_object_mesh_element( doc->cookie, @@ -385,6 +377,7 @@ static bool gmio_amf_write_mesh( gmio_ostringstream_write_chararray(sstream, "'); + gmio_ostringstream_write_chararray(sstream, ">\n"); /* Write volume elements */ if (volume.metadata_count > 0) { - struct gmio_amf_metadata metadata = {0}; - uint32_t imeta; - /* Check function pointer */ if (doc->func_get_object_mesh_volume_metadata == NULL) { return gmio_amf_wcontext_set_error( context, GMIO_AMF_ERROR_NULL_FUNC_GET_OBJECT_MESH_VOLUME_METADATA); } - for (imeta = 0; imeta < volume.metadata_count; ++imeta) { + struct gmio_amf_metadata metadata = {0}; + for (uint32_t imeta = 0; imeta < volume.metadata_count; ++imeta) { doc->func_get_object_mesh_volume_metadata( doc->cookie, &mesh_elt_index, imeta, &metadata); gmio_amf_write_metadata(sstream, &metadata); @@ -415,8 +406,7 @@ static bool gmio_amf_write_mesh( /* Write elements */ if (volume.triangle_count > 0) { struct gmio_amf_triangle triangle = {0}; - uint32_t itri; - for (itri = 0; itri < volume.triangle_count; ++itri) { + for (uint32_t itri = 0; itri < volume.triangle_count; ++itri) { doc->func_get_object_mesh_volume_triangle( doc->cookie, &mesh_elt_index, itri, &triangle); gmio_ostringstream_write_chararray(sstream, ""); @@ -453,26 +443,23 @@ static bool gmio_amf_write_root_objects(struct gmio_amf_wcontext* context) const struct gmio_amf_document* doc = context->document; struct gmio_ostringstream* sstream = context->sstream; struct gmio_amf_object object = {0}; - uint32_t iobj; - for (iobj = 0; iobj < doc->object_count; ++iobj) { + for (uint32_t iobj = 0; iobj < doc->object_count; ++iobj) { doc->func_get_document_element( doc->cookie, GMIO_AMF_DOCUMENT_ELEMENT_OBJECT, iobj, &object); /* Open object element */ gmio_ostringstream_write_chararray(sstream, "'); + gmio_ostringstream_write_chararray(sstream, ">\n"); /* Write metadata elements */ if (object.metadata_count > 0) { - struct gmio_amf_metadata metadata = {0}; - uint32_t imeta; - /* Check function pointer */ if (doc->func_get_document_element_metadata == NULL) { return gmio_amf_wcontext_set_error( context, GMIO_AMF_ERROR_NULL_FUNC_GET_DOCUMENT_ELEMENT_METADATA); } - for (imeta = 0; imeta < object.metadata_count; ++imeta) { + struct gmio_amf_metadata metadata = {0}; + for (uint32_t imeta = 0; imeta < object.metadata_count; ++imeta) { doc->func_get_document_element_metadata( doc->cookie, GMIO_AMF_DOCUMENT_ELEMENT_OBJECT, @@ -488,8 +475,7 @@ static bool gmio_amf_write_root_objects(struct gmio_amf_wcontext* context) /* Write mesh elements */ if (object.mesh_count > 0) { struct gmio_amf_mesh mesh = {0}; - uint32_t imesh; - for (imesh = 0; imesh < object.mesh_count; ++imesh) { + for (uint32_t imesh = 0; imesh < object.mesh_count; ++imesh) { struct gmio_amf_object_mesh_element_index base_mesh_elt_index; doc->func_get_object_mesh(doc->cookie, iobj, imesh, &mesh); base_mesh_elt_index.object_index = iobj; @@ -512,9 +498,7 @@ static bool gmio_amf_write_root_textures(struct gmio_amf_wcontext* context) const struct gmio_amf_document* doc = context->document; struct gmio_ostringstream* sstream = context->sstream; struct gmio_amf_texture texture = {0}; - uint32_t itex; - for (itex = 0; itex < doc->texture_count; ++itex) { - const char* str_texture_type = ""; + for (uint32_t itex = 0; itex < doc->texture_count; ++itex) { doc->func_get_document_element( doc->cookie, GMIO_AMF_DOCUMENT_ELEMENT_TEXTURE, itex, &texture); @@ -525,6 +509,7 @@ static bool gmio_amf_write_root_textures(struct gmio_amf_wcontext* context) gmio_ostringstream_write_xmlattr_u32(sstream, "depth", texture.depth); gmio_ostringstream_write_xmlattr_str( sstream, "tiled", texture.tiled ? "true" : "false"); + const char* str_texture_type = ""; switch (texture.type) { case GMIO_AMF_TEXTURE_TYPE_GRAYSCALE: str_texture_type = "grayscale"; break; @@ -544,31 +529,27 @@ static bool gmio_amf_write_root_textures(struct gmio_amf_wcontext* context) } /* Writes document constellations to stream */ -static bool gmio_amf_write_root_constellations( - struct gmio_amf_wcontext* context) +static bool gmio_amf_write_root_constellations(struct gmio_amf_wcontext* context) { const struct gmio_amf_document* doc = context->document; struct gmio_ostringstream* sstream = context->sstream; struct gmio_amf_constellation constellation = {0}; - uint32_t icons; - for (icons = 0; icons < doc->constellation_count; ++icons) { + for (uint32_t icons = 0; icons < doc->constellation_count; ++icons) { doc->func_get_document_element( doc->cookie, GMIO_AMF_DOCUMENT_ELEMENT_CONSTELLATION, icons, &constellation); gmio_ostringstream_write_chararray(sstream, "'); + gmio_ostringstream_write_chararray(sstream, ">\n"); /* Write constellation elements */ if (constellation.metadata_count > 0) { - struct gmio_amf_metadata metadata = {0}; - uint32_t imeta; - /* Check function pointer */ if (doc->func_get_document_element_metadata == NULL) { return gmio_amf_wcontext_set_error( context, GMIO_AMF_ERROR_NULL_FUNC_GET_DOCUMENT_ELEMENT_METADATA); } - for (imeta = 0; imeta < constellation.metadata_count; ++imeta) { + struct gmio_amf_metadata metadata = {0}; + for (uint32_t imeta = 0; imeta < constellation.metadata_count; ++imeta) { doc->func_get_document_element_metadata( doc->cookie, GMIO_AMF_DOCUMENT_ELEMENT_CONSTELLATION, @@ -581,8 +562,7 @@ static bool gmio_amf_write_root_constellations( /* Write constellation elements */ if (constellation.instance_count > 0) { struct gmio_amf_instance instance = {0}; - uint32_t iinst; - for (iinst = 0; iinst < constellation.instance_count; ++iinst) { + for (uint32_t iinst = 0; iinst < constellation.instance_count; ++iinst) { doc->func_get_constellation_instance( doc->cookie, icons, iinst, &instance); gmio_ostringstream_write_chararray(sstream, "z_stream; size_t total_written_len = 0; int z_retcode = Z_OK; + + context->z_uncompressed_size += len; + context->z_crc32 = crc32(context->z_crc32, (const Bytef*)ptr, len); + z_stream->next_in = (z_const Bytef*)ptr; - z_stream->avail_in = len; /* TODO: use better cast */ + z_stream->avail_in = len; /* Run zlib deflate() on input until output buffer not full * Finish compression when zflush == Z_FINISH */ do { @@ -688,10 +672,16 @@ static size_t gmio_amf_ostringstream_write_zlib( context->error = GMIO_ERROR_UNKNOWN; return total_written_len; } + context->z_compressed_size += total_written_len; return total_written_len; /* zlib official "howto" from http://zlib.net/zlib_how.html */ #if 0 + int ret, flush; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; /* compress until end of file */ do { strm.avail_in = fread(in, 1, CHUNK, source); @@ -702,7 +692,7 @@ static size_t gmio_amf_ostringstream_write_zlib( flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; strm.next_in = in; /* run deflate() on input until output buffer not full, finish - compression if all of source has been read in */ + * compression if all of source has been read in */ do { strm.avail_out = CHUNK; strm.next_out = out; @@ -763,120 +753,145 @@ static intmax_t gmio_amf_task_progress_max(const struct gmio_amf_document* doc) progress_max += doc->material_count; progress_max += doc->texture_count; /* Add total object(vertex_count + edge_count + triangle_count) */ - { - struct gmio_amf_object object = {0}; - uint32_t iobj; - for (iobj = 0; iobj < doc->object_count; ++iobj) { - struct gmio_amf_mesh mesh = {0}; - uint32_t imesh; - doc->func_get_document_element( - doc->cookie, - GMIO_AMF_DOCUMENT_ELEMENT_OBJECT, iobj, &object); - for (imesh = 0; imesh < object.mesh_count; ++imesh) { - doc->func_get_object_mesh(doc->cookie, iobj, imesh, &mesh); - progress_max += mesh.vertex_count; - progress_max += mesh.edge_count; - { - struct gmio_amf_object_mesh_element_index mesh_elt_index; - uint32_t ivol; - mesh_elt_index.object_index = iobj; - mesh_elt_index.mesh_index = imesh; - mesh_elt_index.value = 0; - for (ivol = 0; ivol < mesh.volume_count; ++ivol) { - struct gmio_amf_volume volume = {0}; - mesh_elt_index.value = ivol; - doc->func_get_object_mesh_element( - doc->cookie, - GMIO_AMF_MESH_ELEMENT_VOLUME, - &mesh_elt_index, - &volume); - progress_max += volume.triangle_count; - } - } + struct gmio_amf_object object = {0}; + for (uint32_t iobj = 0; iobj < doc->object_count; ++iobj) { + doc->func_get_document_element( + doc->cookie, + GMIO_AMF_DOCUMENT_ELEMENT_OBJECT, iobj, &object); + struct gmio_amf_mesh mesh = {0}; + for (uint32_t imesh = 0; imesh < object.mesh_count; ++imesh) { + doc->func_get_object_mesh(doc->cookie, iobj, imesh, &mesh); + progress_max += mesh.vertex_count; + progress_max += mesh.edge_count; + struct gmio_amf_object_mesh_element_index mesh_elt_index; + mesh_elt_index.object_index = iobj; + mesh_elt_index.mesh_index = imesh; + mesh_elt_index.value = 0; + for (uint32_t ivol = 0; ivol < mesh.volume_count; ++ivol) { + struct gmio_amf_volume volume = {0}; + mesh_elt_index.value = ivol; + doc->func_get_object_mesh_element( + doc->cookie, + GMIO_AMF_MESH_ELEMENT_VOLUME, + &mesh_elt_index, + &volume); + progress_max += volume.triangle_count; } } } /* Add total constellation(instance_count) */ - { - struct gmio_amf_constellation constellation = {0}; - uint32_t icons; - for (icons = 0; icons < doc->constellation_count; ++icons) { - doc->func_get_document_element( - doc->cookie, - GMIO_AMF_DOCUMENT_ELEMENT_CONSTELLATION, - icons, - &constellation); - progress_max += constellation.instance_count; - } + struct gmio_amf_constellation constellation = {0}; + for (uint32_t icons = 0; icons < doc->constellation_count; ++icons) { + doc->func_get_document_element( + doc->cookie, + GMIO_AMF_DOCUMENT_ELEMENT_CONSTELLATION, + icons, + &constellation); + progress_max += constellation.instance_count; } return progress_max; } +struct gmio_zip_entry_filename { + const char* ptr; + uint16_t len; +}; + +/* Returns a non-null C string for the ZIP entry filename in options */ +static struct gmio_zip_entry_filename gmio_amf_zip_entry_filename( + const struct gmio_amf_write_options* options) +{ + static const char default_filename[] = "geometry.amf"; + const char* filename = options->zip_entry_filename; + const bool is_empty_filename = filename == NULL || *filename == '\0'; + struct gmio_zip_entry_filename zip_filename; + zip_filename.ptr = is_empty_filename ? default_filename : filename; + zip_filename.len = + is_empty_filename ? + sizeof(default_filename) - 1 : + options->zip_entry_filename_len; + return zip_filename; +} + int gmio_amf_write( struct gmio_stream* stream, const struct gmio_amf_document* doc, const struct gmio_amf_write_options* opts) { - /* Constants */ static const struct gmio_amf_write_options default_write_opts = {0}; + opts = opts != NULL ? opts : &default_write_opts; + + /* Constants */ struct gmio_memblock_helper mblock_helper = gmio_memblock_helper(opts != NULL ? &opts->stream_memblock : NULL); const struct gmio_memblock* memblock = &mblock_helper.memblock; + const struct gmio_zip_entry_filename zip_entry_filename = + gmio_amf_zip_entry_filename(opts); /* Variables */ struct gmio_amf_wcontext context = {0}; struct gmio_ostringstream sstream = gmio_ostringstream( *stream, gmio_string(memblock->ptr, 0, memblock->size)); - - opts = opts != NULL ? opts : &default_write_opts; + uintmax_t zip_write_pos = 0; /* Check validity of input parameters */ context.error = GMIO_ERROR_OK; /* TODO: check stream function pointers */ if (!gmio_check_memblock(&context.error, memblock)) goto label_end; - if (!gmio_amf_check_error(&context.error, doc)) + if (!gmio_amf_check_document(&context.error, doc)) goto label_end; /* Initialize writing context */ - { - const struct gmio_string_16 f64_stdio_format = - gmio_to_stdio_float_format( - opts->float64_format, opts->float64_prec); - context.options = opts; - context.sstream = &sstream; - context.document = doc; - context.task_iface = &opts->task_iface; - context.task_progress_current = 0; - if (context.task_iface->func_handle_progress != NULL) - context.task_progress_max += gmio_amf_task_progress_max(doc); - context.f64_format.printf_format = f64_stdio_format.array; - context.f64_format.text_format = opts->float64_format; - context.f64_format.precision = - opts->float64_prec != 0 ? opts->float64_prec : 16; - + const struct gmio_string_16 f64_stdio_format = + gmio_to_stdio_float_format(opts->float64_format, opts->float64_prec); + context.options = opts; + context.sstream = &sstream; + context.document = doc; + context.task_iface = &opts->task_iface; + context.task_progress_current = 0; + if (context.task_iface->func_handle_progress != NULL) + context.task_progress_max += gmio_amf_task_progress_max(doc); + context.f64_format.printf_format = f64_stdio_format.array; + context.f64_format.text_format = opts->float64_format; + context.f64_format.precision = + opts->float64_prec != 0 ? opts->float64_prec : 16; + if (opts->compress) { /* Initialize internal zlib stream for compression */ - if (opts->compress) { - const size_t mblock_halfsize = memblock->size / 2; - context.sstream->strbuff.capacity = mblock_halfsize; - context.z_memblock = - gmio_memblock( - (uint8_t*)memblock->ptr + mblock_halfsize, - mblock_halfsize, - NULL); - context.error = - gmio_zlib_compress_init( - &context.z_stream, &opts->z_compress_options); - if (gmio_error(context.error)) - goto label_end; - context.z_flush = Z_NO_FLUSH; - } + const size_t mblock_halfsize = memblock->size / 2; + context.sstream->strbuff.capacity = mblock_halfsize; + context.z_memblock = + gmio_memblock( + (uint8_t*)memblock->ptr + mblock_halfsize, + mblock_halfsize, + NULL); + context.z_crc32 = crc32(0, NULL, 0); + context.error = + gmio_zlib_compress_init( + &context.z_stream, &opts->z_compress_options); + if (gmio_error(context.error)) + goto label_end; + context.z_flush = Z_NO_FLUSH; } sstream.cookie = &context; sstream.func_stream_write = &gmio_amf_ostringstream_write; + /* If compression enabled, write ZIP local file header */ + if (opts->compress) { + struct gmio_zip_local_file_header info = {0}; + info.general_purpose_flags = + GMIO_ZIP_GENERAL_PURPOSE_FLAG_USE_DATA_DESCRIPTOR; + info.compress_method = GMIO_ZIP_COMPRESS_METHOD_DEFLATE; + info.filename = zip_entry_filename.ptr; + info.filename_len = zip_entry_filename.len; + zip_write_pos += + gmio_zip_write_local_file_header(stream, &info, &context.error); + if (gmio_error(context.error)) + goto label_end; + } + gmio_amf_write_amf_begin(&sstream, doc); if (!gmio_amf_write_root_metadata(&context)) goto label_end; @@ -896,6 +911,49 @@ int gmio_amf_write( gmio_ostringstream_write_chararray(&sstream, "\n"); gmio_ostringstream_flush(&sstream); + /* Write ending ZIP archive data */ + if (opts->compress && gmio_no_error(context.error)) { + zip_write_pos += context.z_compressed_size; + /* Write data descriptor */ + struct gmio_zip_data_descriptor dd = {0}; + dd.crc32 = context.z_crc32; + dd.compressed_size = context.z_compressed_size; + dd.uncompressed_size = context.z_uncompressed_size; + zip_write_pos += + gmio_zip_write_data_descriptor(stream, &dd, &context.error); + if (gmio_error(context.error)) + goto label_end; + /* Write central directory header */ + struct gmio_zip_central_directory_header cdh = {0}; + cdh.version_needed_to_extract = + GMIO_ZIP_FEATURE_VERSION_FILE_COMPRESSED_DEFLATE; + cdh.general_purpose_flags = + GMIO_ZIP_GENERAL_PURPOSE_FLAG_USE_DATA_DESCRIPTOR; + cdh.compress_method = GMIO_ZIP_COMPRESS_METHOD_DEFLATE; + cdh.crc32 = context.z_crc32; + cdh.compressed_size = (uint32_t)context.z_compressed_size; + cdh.uncompressed_size = (uint32_t)context.z_uncompressed_size; + cdh.filename = zip_entry_filename.ptr; + cdh.filename_len = zip_entry_filename.len; + const uintmax_t central_dir_startpos = zip_write_pos; + const size_t central_dir_size = + gmio_zip_write_central_directory_header( + stream, &cdh, &context.error); + zip_write_pos += central_dir_size; + if (gmio_error(context.error)) + goto label_end; + /* Write end of central directory record */ + struct gmio_zip_end_of_central_directory_record eocdr = {0}; + eocdr.total_entry_count_in_central_dir_on_disk = 1; + eocdr.total_entry_count_in_central_dir = 1; + eocdr.central_dir_size = (uint32_t)central_dir_size; + eocdr.start_offset_central_dir_from_disk_start_nb = + (uint32_t)central_dir_startpos; + zip_write_pos += + gmio_zip_write_end_of_central_directory_record( + stream, &eocdr, &context.error); + } + label_end: if (opts->compress) deflateEnd(&context.z_stream); @@ -911,6 +969,8 @@ int gmio_amf_write_file( const bool compress = opts != NULL ? opts->compress : false; FILE* file = fopen(filepath, compress ? "wb" : "w"); if (file != NULL) { + /* TODO: if opts->zip_entry_filename is empty then try to take the + * filename part of filepath */ struct gmio_stream stream = gmio_stream_stdio(file); const int error = gmio_amf_write(&stream, doc, opts); fclose(file); diff --git a/src/gmio_amf/amf_io_options.h b/src/gmio_amf/amf_io_options.h index 74cd9ad..2ab9585 100644 --- a/src/gmio_amf/amf_io_options.h +++ b/src/gmio_amf/amf_io_options.h @@ -76,10 +76,12 @@ struct gmio_amf_write_options */ uint8_t float64_prec; - /* ZIP compression */ + /* ZIP/Deflate compression */ bool compress; struct gmio_zlib_compress_options z_compress_options; + const char* zip_entry_filename; + uint16_t zip_entry_filename_len; }; #endif /* GMIO_AMF_IO_OPTIONS_H */ diff --git a/src/gmio_core/global.h b/src/gmio_core/global.h index a2fe994..c3e6623 100644 --- a/src/gmio_core/global.h +++ b/src/gmio_core/global.h @@ -139,6 +139,11 @@ typedef int32_t intmax_t; typedef uint32_t uintmax_t; # endif +# define INT16_MAX 0x7FFF +# define UINT16_MAX 0xFFFF +# define INT32_MAX 0x7FFFFFFF +# define UINT32_MAX 0xFFFFFFFF + #endif /* GMIO_HAVE_STDBOOL_H */ diff --git a/src/gmio_core/internal/zlib_utils.c b/src/gmio_core/internal/zlib_utils.c index e458ad8..3ad7376 100644 --- a/src/gmio_core/internal/zlib_utils.c +++ b/src/gmio_core/internal/zlib_utils.c @@ -30,21 +30,6 @@ #include "zlib_utils.h" #include "../error.h" -/* Converts zlib error to gmio "zlib-specific" error */ -int zlib_error_to_gmio_error(int z_error) -{ - switch (z_error) { - case Z_OK: return GMIO_ERROR_OK; - case Z_ERRNO: return GMIO_ERROR_ZLIB_ERRNO; - case Z_STREAM_ERROR: return GMIO_ERROR_ZLIB_STREAM; - case Z_DATA_ERROR: return GMIO_ERROR_ZLIB_DATA; - case Z_MEM_ERROR: return GMIO_ERROR_ZLIB_MEM; - case Z_BUF_ERROR: return GMIO_ERROR_ZLIB_BUF; - case Z_VERSION_ERROR: return GMIO_ERROR_ZLIB_VERSION; - } - return GMIO_ERROR_UNKNOWN; -} - static int gmio_to_zlib_compress_level(int gmio_compress_level) { if (gmio_compress_level == GMIO_ZLIB_COMPRESS_LEVEL_DEFAULT) @@ -61,6 +46,21 @@ static int gmio_to_zlib_compress_memusage(int gmio_compress_memusage) return gmio_compress_memusage; } +/* Converts zlib error to gmio "zlib-specific" error */ +int zlib_error_to_gmio_error(int z_error) +{ + switch (z_error) { + case Z_OK: return GMIO_ERROR_OK; + case Z_ERRNO: return GMIO_ERROR_ZLIB_ERRNO; + case Z_STREAM_ERROR: return GMIO_ERROR_ZLIB_STREAM; + case Z_DATA_ERROR: return GMIO_ERROR_ZLIB_DATA; + case Z_MEM_ERROR: return GMIO_ERROR_ZLIB_MEM; + case Z_BUF_ERROR: return GMIO_ERROR_ZLIB_BUF; + case Z_VERSION_ERROR: return GMIO_ERROR_ZLIB_VERSION; + } + return GMIO_ERROR_UNKNOWN; +} + int gmio_zlib_compress_init( struct z_stream_s* z_stream, const struct gmio_zlib_compress_options* z_opts) @@ -69,12 +69,23 @@ int gmio_zlib_compress_init( gmio_to_zlib_compress_level(z_opts->level); const int zlib_compress_memusage = gmio_to_zlib_compress_memusage(z_opts->memory_usage); + /* zlib doc: + * the windowBits parameter is the base two logarithm of the window size + * (the size of the history buffer). It should be in the range 8..15 for + * this version of the library. Larger values of this parameter result in + * better compression at the expense of memory usage. The default value is + * 15 if deflateInit is used instead. + * windowBits can also be –8..–15 for raw deflate. In this case, -windowBits + * determines the window size. deflate() will then generate raw deflate + * data with no zlib header or trailer, and will not compute an adler32 + * check value. */ + static const int z_window_bits = -15; const int z_init_error = deflateInit2_( z_stream, zlib_compress_level, Z_DEFLATED, /* Method */ - 15, /* Window bits(default value) */ + z_window_bits, zlib_compress_memusage, z_opts->strategy, ZLIB_VERSION, diff --git a/tests/test_amf_io.c b/tests/test_amf_io.c index 188daa3..c2e2c4f 100644 --- a/tests/test_amf_io.c +++ b/tests/test_amf_io.c @@ -33,12 +33,15 @@ #include "stream_buffer.h" #include "../src/gmio_core/error.h" +#include "../src/gmio_core/internal/byte_codec.h" #include "../src/gmio_core/internal/helper_stream.h" +#include "../src/gmio_core/internal/zip_utils.h" #include "../src/gmio_amf/amf_error.h" #include "../src/gmio_amf/amf_io.h" #include #include +#include #include struct __tamf__material @@ -108,10 +111,10 @@ static void __tamf__get_object_mesh( uint32_t mesh_index, struct gmio_amf_mesh* ptr_mesh) { - const struct __tamf__document* doc = - (const struct __tamf__document*)cookie; GMIO_UNUSED(object_index); GMIO_UNUSED(mesh_index); + const struct __tamf__document* doc = + (const struct __tamf__document*)cookie; ptr_mesh->vertex_count = doc->mesh.vertex_count; ptr_mesh->edge_count = 0; ptr_mesh->volume_count = 1; @@ -147,9 +150,9 @@ static void __tamf__get_object_mesh_volume_triangle( uint32_t triangle_index, struct gmio_amf_triangle* ptr_triangle) { + GMIO_UNUSED(volume_index); const struct __tamf__document* doc = (const struct __tamf__document*)cookie; const struct __tamf__triangle* tri = &doc->mesh.vec_triangle[triangle_index]; - GMIO_UNUSED(volume_index); ptr_triangle->v1 = tri->vertex[0]; ptr_triangle->v2 = tri->vertex[1]; ptr_triangle->v3 = tri->vertex[2]; @@ -162,14 +165,19 @@ static void __tamf__get_document_element_metadata( uint32_t metadata_index, struct gmio_amf_metadata* ptr_metadata) { - const struct __tamf__document* doc = (const struct __tamf__document*)cookie; GMIO_UNUSED(metadata_index); + const struct __tamf__document* doc = (const struct __tamf__document*)cookie; if (element == GMIO_AMF_DOCUMENT_ELEMENT_MATERIAL) { ptr_metadata->type = "name"; ptr_metadata->data = doc->vec_material[element_index].name; } } +static void __tamf__skip_zip_local_file_header() +{ + +} + static const char* test_amf_write() { { @@ -247,20 +255,71 @@ static const char* test_amf_write() if (gmio_error(error)) printf("\n0x%x\n", error); UTEST_COMPARE_INT(error, GMIO_ERROR_OK); - printf("%s\n", wbuff.ptr); + /* printf("%s\n", wbuff.ptr); */ } -#if 0 - /* Write compressed */ + /* Write compressed ZIP */ { - int error = GMIO_ERROR_OK; + const size_t source_len = wbuff.pos; + uint8_t* source = calloc(source_len, 1); + + memcpy(source, wbuff.ptr, source_len); wbuff.pos = 0; options.compress = true; - error = gmio_amf_write(&stream, &doc, &options); + static const char zip_entry_filename[] = "test.amf"; + options.zip_entry_filename = zip_entry_filename; + options.zip_entry_filename_len = sizeof(zip_entry_filename) - 1; + const int error = gmio_amf_write(&stream, &doc, &options); UTEST_COMPARE_INT(error, GMIO_ERROR_OK); - } +#if 0 + FILE* file = fopen("output.zip", "wb"); + fwrite(wbuff.ptr, 1, wbuff.pos, file); + fclose(file); #endif + /* Unzip and compare with source data */ + uint8_t* rbuff = wbuff.ptr; + /* -- Read local file header */ + UTEST_COMPARE_UINT(gmio_decode_uint32_le(rbuff), 0x04034b50); + rbuff += 8; + /* -- Read compression method */ + UTEST_COMPARE_UINT( + gmio_decode_uint16_le(rbuff), + GMIO_ZIP_COMPRESS_METHOD_DEFLATE); + rbuff += 18; + /* -- Read filename length */ + UTEST_COMPARE_UINT( + gmio_decode_uint16_le(rbuff), + options.zip_entry_filename_len); + rbuff += 2; + /* -- Read extrafield length */ + const uint16_t zip_extrafield_len = gmio_decode_uint16_le(rbuff); + rbuff += 2; + /* -- Read filename */ + UTEST_ASSERT(strncmp( + (const char*)rbuff, + options.zip_entry_filename, + options.zip_entry_filename_len) + == 0); + rbuff += options.zip_entry_filename_len; + /* -- Skip extrafield */ + rbuff += zip_extrafield_len; + +#if 0 /* TODO: check other ZIP records, and uncompress file */ + uint8_t* dest = calloc(wbuffsize, 1); + unsigned long dest_len = (unsigned long)wbuffsize; + const unsigned long z_len = wbuff.pos; + const int zerr = uncompress(dest, &dest_len, wbuff.ptr, z_len); + printf("\n-- Info: z_len=%i src_len=%i\n", z_len, source_len); + UTEST_COMPARE_INT(zerr, Z_OK); + UTEST_COMPARE_UINT(source_len, dest_len); + UTEST_COMPARE_INT(memcmp(dest, source, source_len), 0); + free(dest); +#endif + + free(source); + } + free(wbuff.ptr); }