2015-07-02 11:07:50 +00:00
|
|
|
/*
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
Open Asset Import Library (assimp)
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
|
2021-02-28 11:17:54 +00:00
|
|
|
Copyright (c) 2006-2021, assimp team
|
2018-01-28 18:42:05 +00:00
|
|
|
|
2015-07-02 11:07:50 +00:00
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
Redistribution and use of this software in source and binary forms,
|
|
|
|
with or without modification, are permitted provided that the following
|
|
|
|
conditions are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above
|
|
|
|
copyright notice, this list of conditions and the
|
|
|
|
following disclaimer.
|
|
|
|
|
|
|
|
* Redistributions in binary form must reproduce the above
|
|
|
|
copyright notice, this list of conditions and the
|
|
|
|
following disclaimer in the documentation and/or other
|
|
|
|
materials provided with the distribution.
|
|
|
|
|
|
|
|
* Neither the name of the assimp team, nor the names of its
|
|
|
|
contributors may be used to endorse or promote products
|
|
|
|
derived from this software without specific prior
|
|
|
|
written permission of the assimp team.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @file ImageExtractor.cpp
|
|
|
|
* @brief Implementation of the 'assimp extract' utility
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "Main.h"
|
2020-03-10 23:43:44 +00:00
|
|
|
#include <assimp/ParsingUtils.h>
|
2018-01-06 07:12:40 +00:00
|
|
|
#include <assimp/StringComparison.h>
|
2020-03-10 23:43:44 +00:00
|
|
|
#include <assimp/fast_atof.h>
|
2015-07-02 11:07:50 +00:00
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
static const char *AICMD_MSG_DUMP_HELP_E =
|
|
|
|
"assimp extract <model> [<out>] [-t<n>] [-f<fmt>] [-ba] [-s] [common parameters]\n"
|
|
|
|
"\t -ba Writes BMP's with alpha channel\n"
|
|
|
|
"\t -t<n> Zero-based index of the texture to be extracted \n"
|
|
|
|
"\t -f<f> Specify the file format if <out> is omitted \n"
|
|
|
|
"\t[See the assimp_cmd docs for a full list of all common parameters] \n"
|
|
|
|
"\t -cfast Fast post processing preset, runs just a few important steps \n"
|
|
|
|
"\t -cdefault Default post processing: runs all recommended steps\n"
|
|
|
|
"\t -cfull Fires almost all post processing steps \n";
|
2015-07-02 11:07:50 +00:00
|
|
|
|
|
|
|
#define AI_EXTRACT_WRITE_BMP_ALPHA 0x1
|
|
|
|
#include <assimp/Compiler/pushpack1.h>
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
// Data structure for the first header of a BMP
|
2020-03-10 23:43:44 +00:00
|
|
|
struct BITMAPFILEHEADER {
|
|
|
|
uint16_t bfType;
|
|
|
|
uint32_t bfSize;
|
|
|
|
uint16_t bfReserved1;
|
|
|
|
uint16_t bfReserved2;
|
|
|
|
uint32_t bfOffBits;
|
2015-07-02 11:07:50 +00:00
|
|
|
} PACK_STRUCT;
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
// Data structure for the second header of a BMP
|
2020-03-10 23:43:44 +00:00
|
|
|
struct BITMAPINFOHEADER {
|
|
|
|
int32_t biSize;
|
|
|
|
int32_t biWidth;
|
|
|
|
int32_t biHeight;
|
|
|
|
int16_t biPlanes;
|
|
|
|
int16_t biBitCount;
|
|
|
|
uint32_t biCompression;
|
|
|
|
int32_t biSizeImage;
|
|
|
|
int32_t biXPelsPerMeter;
|
|
|
|
int32_t biYPelsPerMeter;
|
|
|
|
int32_t biClrUsed;
|
|
|
|
int32_t biClrImportant;
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
// pixel data follows header
|
2015-07-02 11:07:50 +00:00
|
|
|
} PACK_STRUCT;
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
// Data structure for the header of a TGA
|
2020-03-10 23:43:44 +00:00
|
|
|
struct TGA_HEADER {
|
|
|
|
uint8_t identsize; // size of ID field that follows 18 byte header (0 usually)
|
|
|
|
uint8_t colourmaptype; // type of colour map 0=none, 1=has palette
|
|
|
|
uint8_t imagetype; // type of image 0=none,1=indexed,2=rgb,3=gray,+8=rle packed
|
|
|
|
|
|
|
|
uint16_t colourmapstart; // first colour map entry in palette
|
|
|
|
uint16_t colourmaplength; // number of colors in palette
|
|
|
|
uint8_t colourmapbits; // number of bits per palette entry 15,16,24,32
|
|
|
|
|
|
|
|
uint16_t xstart; // image x origin
|
|
|
|
uint16_t ystart; // image y origin
|
|
|
|
uint16_t width; // image width in pixels
|
|
|
|
uint16_t height; // image height in pixels
|
|
|
|
uint8_t bits; // image bits per pixel 8,16,24,32
|
|
|
|
uint8_t descriptor; // image descriptor bits (vh flip bits)
|
|
|
|
|
2015-07-02 11:07:50 +00:00
|
|
|
// pixel data follows header
|
|
|
|
} PACK_STRUCT;
|
|
|
|
|
|
|
|
#include <assimp/Compiler/poppack1.h>
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
// Save a texture as bitmap
|
2020-03-10 23:43:44 +00:00
|
|
|
int SaveAsBMP(FILE *file, const aiTexel *data, unsigned int width, unsigned int height, bool SaveAlpha = false) {
|
2015-07-07 22:34:28 +00:00
|
|
|
if (!file || !data) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const unsigned int numc = (SaveAlpha ? 4 : 3);
|
2020-03-10 23:43:44 +00:00
|
|
|
unsigned char *buffer = new unsigned char[width * height * numc];
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
for (unsigned int y = 0; y < height; ++y) {
|
|
|
|
for (unsigned int x = 0; x < width; ++x) {
|
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
unsigned char *s = &buffer[(y * width + x) * numc];
|
|
|
|
const aiTexel *t = &data[y * width + x];
|
2015-07-07 22:34:28 +00:00
|
|
|
s[0] = t->b;
|
|
|
|
s[1] = t->g;
|
|
|
|
s[2] = t->r;
|
|
|
|
if (4 == numc)
|
|
|
|
s[3] = t->a;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BITMAPFILEHEADER header;
|
2020-03-10 23:43:44 +00:00
|
|
|
header.bfType = 'B' | (int('M') << 8u);
|
|
|
|
header.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
|
|
|
|
header.bfSize = header.bfOffBits + width * height * numc;
|
2015-07-07 22:34:28 +00:00
|
|
|
header.bfReserved1 = header.bfReserved2 = 0;
|
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
fwrite(&header, sizeof(BITMAPFILEHEADER), 1, file);
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
BITMAPINFOHEADER info;
|
2020-03-10 23:43:44 +00:00
|
|
|
info.biSize = 40;
|
|
|
|
info.biWidth = width;
|
|
|
|
info.biHeight = height;
|
|
|
|
info.biPlanes = 1;
|
|
|
|
info.biBitCount = (int16_t)numc << 3;
|
2015-07-07 22:34:28 +00:00
|
|
|
info.biCompression = 0;
|
2020-03-10 23:43:44 +00:00
|
|
|
info.biSizeImage = width * height * numc;
|
2015-07-07 22:34:28 +00:00
|
|
|
info.biXPelsPerMeter = 1; // dummy
|
|
|
|
info.biYPelsPerMeter = 1; // dummy
|
|
|
|
info.biClrUsed = 0;
|
|
|
|
info.biClrImportant = 0;
|
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
fwrite(&info, sizeof(BITMAPINFOHEADER), 1, file);
|
2015-07-07 22:34:28 +00:00
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
unsigned char *temp = buffer + info.biSizeImage;
|
|
|
|
const unsigned int row = width * numc;
|
2015-07-07 22:34:28 +00:00
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
for (int y = 0; temp -= row, y < info.biHeight; ++y) {
|
|
|
|
fwrite(temp, row, 1, file);
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// delete the buffer
|
|
|
|
delete[] buffer;
|
|
|
|
return 0;
|
2015-07-02 11:07:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
// Save a texture as tga
|
2020-03-10 23:43:44 +00:00
|
|
|
int SaveAsTGA(FILE *file, const aiTexel *data, unsigned int width, unsigned int height) {
|
2015-07-07 22:34:28 +00:00
|
|
|
if (!file || !data) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
TGA_HEADER head;
|
|
|
|
memset(&head, 0, sizeof(head));
|
2020-03-10 23:43:44 +00:00
|
|
|
head.bits = 32;
|
2015-07-07 22:34:28 +00:00
|
|
|
head.height = (uint16_t)height;
|
2020-03-10 23:43:44 +00:00
|
|
|
head.width = (uint16_t)width;
|
|
|
|
head.descriptor |= (1u << 5);
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
head.imagetype = 2; // actually it's RGBA
|
2020-03-10 23:43:44 +00:00
|
|
|
fwrite(&head, sizeof(TGA_HEADER), 1, file);
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
for (unsigned int y = 0; y < height; ++y) {
|
|
|
|
for (unsigned int x = 0; x < width; ++x) {
|
2020-03-10 23:43:44 +00:00
|
|
|
fwrite(data + y * width + x, 4, 1, file);
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2015-07-02 11:07:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
// Do the texture import for a given aiTexture
|
2020-03-11 16:42:10 +00:00
|
|
|
int DoExport(const aiTexture *tx, FILE *p, const std::string &extension, unsigned int flags) {
|
2015-07-07 22:34:28 +00:00
|
|
|
// export the image to the appropriate decoder
|
|
|
|
if (extension == "bmp") {
|
2020-03-10 23:43:44 +00:00
|
|
|
SaveAsBMP(p, tx->pcData, tx->mWidth, tx->mHeight,
|
|
|
|
(0 != (flags & AI_EXTRACT_WRITE_BMP_ALPHA)));
|
|
|
|
} else if (extension == "tga") {
|
|
|
|
SaveAsTGA(p, tx->pcData, tx->mWidth, tx->mHeight);
|
|
|
|
} else {
|
2015-07-07 22:34:28 +00:00
|
|
|
printf("assimp extract: No available texture encoder found for %s\n", extension.c_str());
|
2020-01-26 18:10:21 +00:00
|
|
|
return AssimpCmdExtractError::NoAvailableTextureEncoderFound;
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
2020-01-26 18:10:21 +00:00
|
|
|
return AssimpCmdError::Success;
|
2015-07-02 11:07:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
// Implementation of the assimp extract utility
|
2020-03-10 23:43:44 +00:00
|
|
|
int Assimp_Extract(const char *const *params, unsigned int num) {
|
|
|
|
const char *const invalid = "assimp extract: Invalid number of arguments. See \'assimp extract --help\'\n";
|
2019-03-30 18:12:13 +00:00
|
|
|
// assimp extract in out [options]
|
2015-07-07 22:34:28 +00:00
|
|
|
if (num < 1) {
|
|
|
|
printf(invalid);
|
2020-01-26 18:10:21 +00:00
|
|
|
return AssimpCmdError::InvalidNumberOfArguments;
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// --help
|
2020-03-10 23:43:44 +00:00
|
|
|
if (!strcmp(params[0], "-h") || !strcmp(params[0], "--help") || !strcmp(params[0], "-?")) {
|
|
|
|
printf("%s", AICMD_MSG_DUMP_HELP_E);
|
2020-01-26 18:10:21 +00:00
|
|
|
return AssimpCmdError::Success;
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
std::string in = std::string(params[0]);
|
2015-07-07 22:34:28 +00:00
|
|
|
std::string out = (num > 1 ? std::string(params[1]) : "-");
|
|
|
|
|
|
|
|
// get import flags
|
|
|
|
ImportData import;
|
2020-03-10 23:43:44 +00:00
|
|
|
ProcessStandardArguments(import, params + 1, num - 1);
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
bool nosuffix = false;
|
2020-03-10 23:43:44 +00:00
|
|
|
unsigned int texIdx = 0xffffffff, flags = 0;
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
// process other flags
|
|
|
|
std::string extension = "bmp";
|
2020-03-10 23:43:44 +00:00
|
|
|
for (unsigned int i = (out[0] == '-' ? 1 : 2); i < num; ++i) {
|
2015-07-07 22:34:28 +00:00
|
|
|
if (!params[i]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
if (!strncmp(params[i], "-f", 2)) {
|
|
|
|
extension = std::string(params[i] + 2);
|
|
|
|
} else if (!strncmp(params[i], "--format=", 9)) {
|
|
|
|
extension = std::string(params[i] + 9);
|
|
|
|
} else if (!strcmp(params[i], "--nosuffix") || !strcmp(params[i], "-s")) {
|
2015-07-07 22:34:28 +00:00
|
|
|
nosuffix = true;
|
2020-03-10 23:43:44 +00:00
|
|
|
} else if (!strncmp(params[i], "--texture=", 10)) {
|
|
|
|
texIdx = Assimp::strtoul10(params[i] + 10);
|
|
|
|
} else if (!strncmp(params[i], "-t", 2)) {
|
|
|
|
texIdx = Assimp::strtoul10(params[i] + 2);
|
|
|
|
} else if (!strcmp(params[i], "-ba") || !strcmp(params[i], "--bmp-with-alpha")) {
|
2015-07-07 22:34:28 +00:00
|
|
|
flags |= AI_EXTRACT_WRITE_BMP_ALPHA;
|
|
|
|
}
|
2015-07-02 11:07:50 +00:00
|
|
|
#if 0
|
2015-07-07 22:34:28 +00:00
|
|
|
else {
|
|
|
|
printf("Unknown parameter: %s\n",params[i]);
|
|
|
|
return 10;
|
|
|
|
}
|
2015-07-02 11:07:50 +00:00
|
|
|
#endif
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
|
2021-03-09 20:08:28 +00:00
|
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ai_tolower<char>);
|
2020-03-10 23:43:44 +00:00
|
|
|
|
2015-07-07 22:34:28 +00:00
|
|
|
if (out[0] == '-') {
|
|
|
|
// take file name from input file
|
|
|
|
std::string::size_type s = in.find_last_of('.');
|
|
|
|
if (s == std::string::npos)
|
|
|
|
s = in.length();
|
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
out = in.substr(0, s);
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// take file extension from file name, if given
|
|
|
|
std::string::size_type s = out.find_last_of('.');
|
|
|
|
if (s != std::string::npos) {
|
2020-03-10 23:43:44 +00:00
|
|
|
extension = out.substr(s + 1, in.length() - (s + 1));
|
|
|
|
out = out.substr(0, s);
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// import the main model
|
2020-03-10 23:43:44 +00:00
|
|
|
const aiScene *scene = ImportModel(import, in);
|
2015-07-07 22:34:28 +00:00
|
|
|
if (!scene) {
|
2020-03-10 23:43:44 +00:00
|
|
|
printf("assimp extract: Unable to load input file %s\n", in.c_str());
|
2020-01-26 18:10:21 +00:00
|
|
|
return AssimpCmdError::FailedToLoadInputFile;
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// get the texture(s) to be exported
|
|
|
|
if (texIdx != 0xffffffff) {
|
|
|
|
|
|
|
|
// check whether the requested texture is existing
|
|
|
|
if (texIdx >= scene->mNumTextures) {
|
|
|
|
::printf("assimp extract: Texture %i requested, but there are just %i textures\n",
|
2020-03-10 23:43:44 +00:00
|
|
|
texIdx, scene->mNumTextures);
|
2020-01-26 18:10:21 +00:00
|
|
|
return AssimpCmdExtractError::TextureIndexIsOutOfRange;
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
2020-03-10 23:43:44 +00:00
|
|
|
} else {
|
|
|
|
::printf("assimp extract: Exporting %i textures\n", scene->mNumTextures);
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// now write all output textures
|
2020-03-10 23:43:44 +00:00
|
|
|
for (unsigned int i = 0; i < scene->mNumTextures; ++i) {
|
2015-07-07 22:34:28 +00:00
|
|
|
if (texIdx != 0xffffffff && texIdx != i) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
const aiTexture *tex = scene->mTextures[i];
|
2015-07-07 22:34:28 +00:00
|
|
|
std::string out_cpy = out, out_ext = extension;
|
|
|
|
|
|
|
|
// append suffix if necessary - always if all textures are exported
|
|
|
|
if (!nosuffix || (texIdx == 0xffffffff)) {
|
2020-03-10 23:43:44 +00:00
|
|
|
out_cpy.append("_img");
|
2015-07-07 22:34:28 +00:00
|
|
|
char tmp[10];
|
2020-03-10 23:43:44 +00:00
|
|
|
Assimp::ASSIMP_itoa10(tmp, i);
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
out_cpy.append(std::string(tmp));
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the texture is a compressed one, we'll export
|
|
|
|
// it to its native file format
|
|
|
|
if (!tex->mHeight) {
|
|
|
|
printf("assimp extract: Texture %i is compressed (%s). Writing native file format.\n",
|
2020-03-10 23:43:44 +00:00
|
|
|
i, tex->achFormatHint);
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
// modify file extension
|
|
|
|
out_ext = std::string(tex->achFormatHint);
|
|
|
|
}
|
2020-03-10 23:43:44 +00:00
|
|
|
out_cpy.append("." + out_ext);
|
2015-07-07 22:34:28 +00:00
|
|
|
|
|
|
|
// open output file
|
2020-03-10 23:43:44 +00:00
|
|
|
FILE *p = ::fopen(out_cpy.c_str(), "wb");
|
|
|
|
if (!p) {
|
|
|
|
printf("assimp extract: Unable to open output file %s\n", out_cpy.c_str());
|
2020-01-26 18:10:21 +00:00
|
|
|
return AssimpCmdError::FailedToOpenOutputFile;
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
int m;
|
|
|
|
|
|
|
|
if (!tex->mHeight) {
|
2020-03-10 23:43:44 +00:00
|
|
|
m = (1 != fwrite(tex->pcData, tex->mWidth, 1, p)) ?
|
|
|
|
static_cast<int>(AssimpCmdError::Success) :
|
|
|
|
static_cast<int>(AssimpCmdExtractError::FailedToExportCompressedTexture);
|
|
|
|
} else {
|
|
|
|
m = DoExport(tex, p, extension, flags);
|
2015-07-07 22:34:28 +00:00
|
|
|
}
|
|
|
|
::fclose(p);
|
|
|
|
|
2020-03-10 23:43:44 +00:00
|
|
|
printf("assimp extract: Wrote texture %i to %s\n", i, out_cpy.c_str());
|
|
|
|
if (texIdx != 0xffffffff) {
|
2015-07-07 22:34:28 +00:00
|
|
|
return m;
|
2020-03-10 23:43:44 +00:00
|
|
|
}
|
2020-03-11 16:42:10 +00:00
|
|
|
}
|
|
|
|
|
2020-01-26 18:10:21 +00:00
|
|
|
return AssimpCmdError::Success;
|
2015-07-02 11:07:50 +00:00
|
|
|
}
|