Skip to content

Commit

Permalink
perf: jpeg2000 valid_file implementation, much faster than trying to …
Browse files Browse the repository at this point in the history
…open (AcademySoftwareFoundation#4548)

Primary cost of detecting whether a given file is valid jpeg2000 file is
the thread pool initialization and shutdown that `Jpeg2000Input::open`
does (the actual thread pool part is inside OpenJpeg code).

So add a dedicated `valid_file()` implementation that only needs to
check 12 bytes of the header. While at it, I changed already existing
`isJp2File()` to `is_jp2_header()` to better match naming conventions
used elsewhere, and instead of trying to handle both little and big
endian cases by manual repetition of two sets of magic integers, let's
do just byte comparisons with `memcmp` instead.

On my PC (Ryzen 5950X, SSD, Windows VS2022), doing
`ImageInput::create()` on 1138 files where they are not images at all
(so OIIO in turns tries all the input plugins on them):

- Before: **3.4 seconds** spent in `Jpeg2000Input::open` (1.9s
`opj_thread_pool_create`, 1.3s `opj_thread_pool_destroy`)
- After: **33 milliseconds** spent in `Jpeg2000Input::valid_file`

No new tests. I checked behavior on the official Jpeg2000 conformance
data set
(https://github.com/uclouvain/openjpeg-data/tree/master/input/conformance),
seems to work.


Signed-off-by: Aras Pranckevicius <[email protected]>
  • Loading branch information
aras-p authored and lgritz committed Dec 1, 2024
1 parent 8f315bd commit 6717e03
Showing 1 changed file with 30 additions and 20 deletions.
50 changes: 30 additions & 20 deletions src/jpeg2000.imageio/jpeg2000input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class Jpeg2000Input final : public ImageInput {
return feature == "ioproxy";
// FIXME: we should support Exif/IPTC, but currently don't.
}
bool valid_file(Filesystem::IOProxy* ioproxy) const override;
bool open(const std::string& name, ImageSpec& spec) override;
bool open(const std::string& name, ImageSpec& newspec,
const ImageSpec& config) override;
Expand All @@ -97,7 +98,8 @@ class Jpeg2000Input final : public ImageInput {

void init(void);

bool isJp2File(const int* const p_magicTable) const;
static bool is_jp2_header(const uint8_t header[12]);
static bool is_j2k_header(const uint8_t header[5]);

opj_codec_t* create_decompressor();
void destroy_decompressor();
Expand Down Expand Up @@ -208,7 +210,19 @@ Jpeg2000Input::init(void)
ioproxy_clear();
}

bool
Jpeg2000Input::valid_file(Filesystem::IOProxy* ioproxy) const
{
if (!ioproxy || ioproxy->mode() != Filesystem::IOProxy::Mode::Read)
return false;

uint8_t header[12];
auto r = ioproxy->pread(header, sizeof(header), 0);
if (r != sizeof(header)) {
return false;
}
return is_jp2_header(header) || is_j2k_header(header);
}

bool
Jpeg2000Input::open(const std::string& name, ImageSpec& p_spec)
Expand Down Expand Up @@ -409,36 +423,32 @@ Jpeg2000Input::close(void)
return true;
}


bool
Jpeg2000Input::isJp2File(const int* const p_magicTable) const
Jpeg2000Input::is_jp2_header(const uint8_t header[12])
{
const int32_t JP2_MAGIC = 0x0000000C, JP2_MAGIC2 = 0x0C000000;
if (p_magicTable[0] == JP2_MAGIC || p_magicTable[0] == JP2_MAGIC2) {
const int32_t JP2_SIG1_MAGIC = 0x6A502020, JP2_SIG1_MAGIC2 = 0x2020506A;
const int32_t JP2_SIG2_MAGIC = 0x0D0A870A, JP2_SIG2_MAGIC2 = 0x0A870A0D;
if ((p_magicTable[1] == JP2_SIG1_MAGIC
|| p_magicTable[1] == JP2_SIG1_MAGIC2)
&& (p_magicTable[2] == JP2_SIG2_MAGIC
|| p_magicTable[2] == JP2_SIG2_MAGIC2)) {
return true;
}
}
return false;
const uint8_t jp2_header[] = { 0x0, 0x0, 0x0, 0x0C, 0x6A, 0x50,
0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A };
return memcmp(header, jp2_header, sizeof(jp2_header)) == 0;
}

bool
Jpeg2000Input::is_j2k_header(const uint8_t header[5])
{
const uint8_t j2k_header[] = { 0xFF, 0x4F, 0xFF, 0x51, 0x00 };
return memcmp(header, j2k_header, sizeof(j2k_header)) == 0;
}

opj_codec_t*
Jpeg2000Input::create_decompressor()
{
int magic[3];
auto r = ioproxy()->pread(magic, sizeof(magic), 0);
if (r != 3 * sizeof(int)) {
uint8_t header[12];
auto r = ioproxy()->pread(header, sizeof(header), 0);
if (r != sizeof(header)) {
errorfmt("Empty file \"{}\"", m_filename);
return nullptr;
}
return opj_create_decompress(isJp2File(magic) ? OPJ_CODEC_JP2
: OPJ_CODEC_J2K);
return opj_create_decompress(is_jp2_header(header) ? OPJ_CODEC_JP2
: OPJ_CODEC_J2K);
}


Expand Down

0 comments on commit 6717e03

Please sign in to comment.