Skip to content

Commit dd8c7ef

Browse files
author
Steve Canny
committed
img: add BMP support
1 parent 9a2296e commit dd8c7ef

File tree

8 files changed

+85
-6
lines changed

8 files changed

+85
-6
lines changed

docx/image/bmp.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import absolute_import, division, print_function
44

5+
from .constants import MIME_TYPE
6+
from .helpers import LITTLE_ENDIAN, StreamReader
57
from .image import BaseImageHeader
68

79

@@ -12,7 +14,36 @@ class Bmp(BaseImageHeader):
1214
@classmethod
1315
def from_stream(cls, stream):
1416
"""
15-
Return |Bmp| instance having header properties parsed from BMP image
16-
in *stream*.
17+
Return |Bmp| instance having header properties parsed from the BMP
18+
image in *stream*.
1719
"""
18-
return cls(None, None, None, None)
20+
stream_rdr = StreamReader(stream, LITTLE_ENDIAN)
21+
22+
px_width = stream_rdr.read_long(0x12)
23+
px_height = stream_rdr.read_long(0x16)
24+
25+
horz_px_per_meter = stream_rdr.read_long(0x26)
26+
vert_px_per_meter = stream_rdr.read_long(0x2A)
27+
28+
horz_dpi = cls._dpi(horz_px_per_meter)
29+
vert_dpi = cls._dpi(vert_px_per_meter)
30+
31+
return cls(px_width, px_height, horz_dpi, vert_dpi)
32+
33+
@property
34+
def content_type(self):
35+
"""
36+
MIME content type for this image, unconditionally `image/bmp` for
37+
BMP images.
38+
"""
39+
return MIME_TYPE.BMP
40+
41+
@staticmethod
42+
def _dpi(px_per_meter):
43+
"""
44+
Return the integer pixels per inch from *px_per_meter*, defaulting to
45+
96 if *px_per_meter* is zero.
46+
"""
47+
if px_per_meter == 0:
48+
return 96
49+
return int(round(px_per_meter * 0.0254))

docx/image/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class MIME_TYPE(object):
9797
"""
9898
Image content types
9999
"""
100+
BMP = 'image/bmp'
100101
GIF = 'image/gif'
101102
JPEG = 'image/jpeg'
102103
PNG = 'image/png'

docx/image/gif.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ def from_stream(cls, stream):
2626
@property
2727
def content_type(self):
2828
"""
29-
MIME content type for this image, unconditionally `image/png` for
30-
PNG images.
29+
MIME content type for this image, unconditionally `image/gif` for
30+
GIF images.
3131
"""
3232
return MIME_TYPE.GIF
3333

docx/image/image.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,11 @@ def content_type(self):
126126
"""
127127
Abstract property definition, must be implemented by all subclasses.
128128
"""
129-
raise NotImplementedError(
129+
msg = (
130130
'content_type property must be implemented by all subclasses of '
131131
'BaseImageHeader'
132132
)
133+
raise NotImplementedError(msg)
133134

134135
@property
135136
def px_width(self):

features/img-characterize-image.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ Feature: Characterize an image file
2323
| jpeg420exif.jpg | image/jpeg | 2048 | 1536 | 72 | 72 |
2424
| court-exif.jpg | image/jpeg | 500 | 375 | 256 | 256 |
2525
| lena.gif | image/gif | 256 | 256 | 72 | 72 |
26+
| lena.bmp | image/bmp | 512 | 512 | 96 | 96 |
27+
| mountain.bmp | image/bmp | 640 | 480 | 300 | 300 |

features/steps/test_files/lena.bmp

257 KB
Binary file not shown.
301 KB
Binary file not shown.

tests/image/test_bmp.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Test suite for docx.image.bmp module
5+
"""
6+
7+
from __future__ import absolute_import, print_function
8+
9+
import pytest
10+
11+
from docx.compat import BytesIO
12+
from docx.image.constants import MIME_TYPE
13+
from docx.image.bmp import Bmp
14+
15+
from ..unitutil import initializer_mock
16+
17+
18+
class DescribeBmp(object):
19+
20+
def it_can_construct_from_a_bmp_stream(self, from_stream_fixture):
21+
stream, Bmp__init__, cx, cy, horz_dpi, vert_dpi = from_stream_fixture
22+
bmp = Bmp.from_stream(stream)
23+
Bmp__init__.assert_called_once_with(cx, cy, horz_dpi, vert_dpi)
24+
assert isinstance(bmp, Bmp)
25+
26+
def it_knows_its_content_type(self):
27+
bmp = Bmp(None, None, None, None)
28+
assert bmp.content_type == MIME_TYPE.BMP
29+
30+
# fixtures -------------------------------------------------------
31+
32+
@pytest.fixture
33+
def from_stream_fixture(self, Bmp__init__):
34+
cx, cy, horz_dpi, vert_dpi = 26, 43, 200, 96
35+
bytes_ = (
36+
b'fillerfillerfiller\x1A\x00\x00\x00\x2B\x00\x00\x00'
37+
b'fillerfiller\xB8\x1E\x00\x00\x00\x00\x00\x00'
38+
)
39+
stream = BytesIO(bytes_)
40+
return stream, Bmp__init__, cx, cy, horz_dpi, vert_dpi
41+
42+
@pytest.fixture
43+
def Bmp__init__(self, request):
44+
return initializer_mock(request, Bmp)

0 commit comments

Comments
 (0)