Success with default conversion from j2c to jp2.
This commit is contained in:
parent
19336fc15e
commit
2bb1d874b0
3 changed files with 127 additions and 12 deletions
|
|
@ -167,7 +167,7 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
colorspace is not None, then icc_profile must be empty.
|
colorspace is not None, then icc_profile must be empty.
|
||||||
"""
|
"""
|
||||||
def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0,
|
def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0,
|
||||||
approximation=1, colorspace=None, icc_profile=None, **kwargs):
|
approximation=0, colorspace=None, icc_profile=None, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='Colour Specification')
|
Jp2kBox.__init__(self, id='', longname='Colour Specification')
|
||||||
|
|
||||||
if colorspace is not None and icc_profile is not None:
|
if colorspace is not None and icc_profile is not None:
|
||||||
|
|
@ -208,6 +208,20 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write an Colour Specification box to file.
|
||||||
|
"""
|
||||||
|
length = 15 if self.icc_profile is None else 11 + len(self.icc_profile)
|
||||||
|
f.write(struct.pack('>I', length))
|
||||||
|
f.write('colr'.encode())
|
||||||
|
|
||||||
|
buffer = struct.pack('>BBBI',
|
||||||
|
self.method,
|
||||||
|
self.precedence,
|
||||||
|
self.approximation,
|
||||||
|
self.colorspace)
|
||||||
|
f.write(buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
"""Parse JPEG 2000 color specification box.
|
"""Parse JPEG 2000 color specification box.
|
||||||
|
|
@ -756,6 +770,25 @@ class ImageHeaderBox(Jp2kBox):
|
||||||
self.colorspace_unknown)
|
self.colorspace_unknown)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
def _write(self, f):
|
||||||
|
"""Write an Image Header box to file.
|
||||||
|
"""
|
||||||
|
f.write(struct.pack('>I', 22))
|
||||||
|
f.write('ihdr'.encode())
|
||||||
|
|
||||||
|
# signedness and bps are stored together in a single byte
|
||||||
|
bit_depth_signedness = 0x80 if self.signed else 0x00
|
||||||
|
bit_depth_signedness |= self.bits_per_component - 1
|
||||||
|
buffer = struct.pack('>IIHBBBB',
|
||||||
|
self.height,
|
||||||
|
self.width,
|
||||||
|
self.num_components,
|
||||||
|
bit_depth_signedness,
|
||||||
|
self.compression,
|
||||||
|
1 if self.colorspace_unknown else 0,
|
||||||
|
1 if self.ip_provided else 0)
|
||||||
|
f.write(buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(f, id, offset, length):
|
def _parse(f, id, offset, length):
|
||||||
"""Parse JPEG 2000 image header box.
|
"""Parse JPEG 2000 image header box.
|
||||||
|
|
|
||||||
|
|
@ -407,17 +407,37 @@ class Jp2k(Jp2kBox):
|
||||||
filename : str
|
filename : str
|
||||||
JP2 file to be created from a raw codestream.
|
JP2 file to be created from a raw codestream.
|
||||||
"""
|
"""
|
||||||
|
if len(self.box) > 0:
|
||||||
|
msg = "This method can only be used on files consisting only of "
|
||||||
|
msg += "a raw codestream. It cannot be used on a file that "
|
||||||
|
msg += "already has the JP2 format."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
boxes = [JPEG2000SignatureBox(),
|
boxes = [JPEG2000SignatureBox(),
|
||||||
FileTypeBox(),
|
FileTypeBox(),
|
||||||
JP2HeaderBox(),
|
JP2HeaderBox()]
|
||||||
ContiguousCodestreamBox()]
|
c = self.get_codestream()
|
||||||
boxes[2].box = [ImageHeaderBox(height=1456,
|
height = c.segment[1].Ysiz
|
||||||
width=2592,
|
width = c.segment[1].Xsiz
|
||||||
num_components=3),
|
num_components = len(c.segment[1].XRsiz)
|
||||||
|
boxes[2].box = [ImageHeaderBox(height=height,
|
||||||
|
width=width,
|
||||||
|
num_components=num_components),
|
||||||
ColourSpecificationBox(colorspace=SRGB)]
|
ColourSpecificationBox(colorspace=SRGB)]
|
||||||
with open(filename, 'wb') as fp:
|
with open(filename, 'wb') as ofile:
|
||||||
for box in boxes:
|
for box in boxes:
|
||||||
box._write(fp)
|
box._write(ofile)
|
||||||
|
|
||||||
|
# The codestream gets written last.
|
||||||
|
ofile.write(struct.pack('>I', self.length + 8))
|
||||||
|
ofile.write('jp2c'.encode())
|
||||||
|
|
||||||
|
with open(self.filename, 'rb') as ifile:
|
||||||
|
ofile.write(ifile.read())
|
||||||
|
|
||||||
|
ofile.flush()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False):
|
def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False):
|
||||||
"""Read a JPEG 2000 image.
|
"""Read a JPEG 2000 image.
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class TestJp2Boxes(unittest.TestCase):
|
||||||
j = Jp2k(jp2file)
|
j = Jp2k(jp2file)
|
||||||
c = [box for box in j.box if box.id == 'jp2c'][0]
|
c = [box for box in j.box if box.id == 'jp2c'][0]
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as ofile:
|
with tempfile.NamedTemporaryFile(suffix='.j2c', delete=False) as ofile:
|
||||||
with open(jp2file, 'rb') as ifile:
|
with open(jp2file, 'rb') as ifile:
|
||||||
# Everything up until the jp2c box.
|
# Everything up until the jp2c box.
|
||||||
ifile.seek(c.offset+8)
|
ifile.seek(c.offset+8)
|
||||||
|
|
@ -74,7 +74,7 @@ class TestJp2Boxes(unittest.TestCase):
|
||||||
b = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB)
|
b = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.SRGB)
|
||||||
self.assertEqual(b.method, glymur.core.ENUMERATED_COLORSPACE)
|
self.assertEqual(b.method, glymur.core.ENUMERATED_COLORSPACE)
|
||||||
self.assertEqual(b.precedence, 0)
|
self.assertEqual(b.precedence, 0)
|
||||||
self.assertEqual(b.approximation, 1)
|
self.assertEqual(b.approximation, 0)
|
||||||
self.assertEqual(b.colorspace, glymur.core.SRGB)
|
self.assertEqual(b.colorspace, glymur.core.SRGB)
|
||||||
self.assertIsNone(b.icc_profile)
|
self.assertIsNone(b.icc_profile)
|
||||||
|
|
||||||
|
|
@ -108,9 +108,71 @@ class TestJp2Boxes(unittest.TestCase):
|
||||||
self.assertEqual(b.main_header, [])
|
self.assertEqual(b.main_header, [])
|
||||||
|
|
||||||
def test_tojp2(self):
|
def test_tojp2(self):
|
||||||
j = Jp2k(self.raw_codestream)
|
j2k = Jp2k(self.raw_codestream)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2", mode="wb") as tfile:
|
||||||
|
j2k.tojp2(tfile.name)
|
||||||
|
|
||||||
|
jp2 = Jp2k(tfile.name)
|
||||||
|
self.assertEqual(len(jp2.box), 4)
|
||||||
|
|
||||||
|
self.assertEqual(jp2.box[0].id, 'jP ')
|
||||||
|
self.assertEqual(jp2.box[0].offset, 0)
|
||||||
|
self.assertEqual(jp2.box[0].length, 12)
|
||||||
|
self.assertEqual(jp2.box[0].longname, 'JPEG 2000 Signature')
|
||||||
|
|
||||||
|
self.assertEqual(jp2.box[1].id, 'ftyp')
|
||||||
|
self.assertEqual(jp2.box[1].offset, 12)
|
||||||
|
self.assertEqual(jp2.box[1].length, 20)
|
||||||
|
self.assertEqual(jp2.box[1].longname, 'File Type')
|
||||||
|
|
||||||
|
self.assertEqual(jp2.box[2].id, 'jp2h')
|
||||||
|
self.assertEqual(jp2.box[2].offset, 32)
|
||||||
|
self.assertEqual(jp2.box[2].length, 45)
|
||||||
|
self.assertEqual(jp2.box[2].longname, 'JP2 Header')
|
||||||
|
|
||||||
|
self.assertEqual(jp2.box[3].id, 'jp2c')
|
||||||
|
self.assertEqual(jp2.box[3].offset, 77)
|
||||||
|
self.assertEqual(jp2.box[3].length, 1133427)
|
||||||
|
|
||||||
|
# jp2h super box
|
||||||
|
self.assertEqual(len(jp2.box[2].box), 2)
|
||||||
|
|
||||||
|
self.assertEqual(jp2.box[2].box[0].id, 'ihdr')
|
||||||
|
self.assertEqual(jp2.box[2].box[0].offset, 40)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].length, 22)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].longname, 'Image Header')
|
||||||
|
self.assertEqual(jp2.box[2].box[0].height, 1456)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].width, 2592)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].num_components, 3)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].bits_per_component, 8)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].signed, False)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].compression, 7)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].colorspace_unknown, False)
|
||||||
|
self.assertEqual(jp2.box[2].box[0].ip_provided, False)
|
||||||
|
|
||||||
|
self.assertEqual(jp2.box[2].box[1].id, 'colr')
|
||||||
|
self.assertEqual(jp2.box[2].box[1].offset, 62)
|
||||||
|
self.assertEqual(jp2.box[2].box[1].length, 15)
|
||||||
|
self.assertEqual(jp2.box[2].box[1].longname, 'Colour Specification')
|
||||||
|
self.assertEqual(jp2.box[2].box[1].precedence, 0)
|
||||||
|
self.assertEqual(jp2.box[2].box[1].approximation, 0)
|
||||||
|
self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB)
|
||||||
|
self.assertIsNone(jp2.box[2].box[1].icc_profile)
|
||||||
|
|
||||||
|
def test_image_header_box_not_first_in_jp2_header(self):
|
||||||
|
# The specification says that ihdr must be the first box in jp2h.
|
||||||
|
self.assertTrue(False)
|
||||||
|
|
||||||
|
def test_color_specification_box_with_icc_profile(self):
|
||||||
|
# Not sure how this should be done so don't allow it.
|
||||||
|
self.assertTrue(False)
|
||||||
|
|
||||||
|
def test_tojp2_on_jp2(self):
|
||||||
|
# Should not use "tojp2" method on file that is already jp2.
|
||||||
|
j = glymur.Jp2k(self.jp2file)
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
j.tojp2(tfile.name)
|
with self.assertRaises(IOError):
|
||||||
|
j.tojp2(tfile.name)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue