Skip to content

Commit 994b290

Browse files
author
Steve Canny
committed
img: add ImagePart cx, cy from loaded part
Along the way: * add Image.from_blob() constructor * add ImagePart.image lazyproperty
1 parent 8a0d893 commit 994b290

File tree

2 files changed

+41
-11
lines changed

2 files changed

+41
-11
lines changed

docx/parts/image.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
except ImportError:
1717
import Image as PIL_Image
1818

19+
from StringIO import StringIO
20+
1921
from docx.opc.constants import CONTENT_TYPE as CT
2022
from docx.opc.package import Part
2123
from docx.shared import Emu, Inches, lazyproperty
@@ -68,6 +70,19 @@ def filename(self):
6870
"""
6971
return self._filename
7072

73+
@classmethod
74+
def from_blob(cls, blob):
75+
stream = StringIO(blob)
76+
content_type, px_width, px_height, horz_dpi, vert_dpi = (
77+
cls._analyze_image(stream)
78+
)
79+
stream.close()
80+
filename = 'image%s' % cls._def_mime_ext(content_type)
81+
return cls(
82+
blob, filename, content_type, px_width, px_height, horz_dpi,
83+
vert_dpi
84+
)
85+
7186
@property
7287
def horz_dpi(self):
7388
"""
@@ -208,8 +223,8 @@ def default_cx(self):
208223
Native width of this image, calculated from its width in pixels and
209224
horizontal dots per inch (dpi).
210225
"""
211-
px_width = self._image.px_width
212-
horz_dpi = self._image.horz_dpi
226+
px_width = self.image.px_width
227+
horz_dpi = self.image.horz_dpi
213228
width_in_inches = px_width / horz_dpi
214229
return Inches(width_in_inches)
215230

@@ -219,8 +234,8 @@ def default_cy(self):
219234
Native height of this image, calculated from its height in pixels and
220235
vertical dots per inch (dpi).
221236
"""
222-
px_height = self._image.px_height
223-
horz_dpi = self._image.horz_dpi
237+
px_height = self.image.px_height
238+
horz_dpi = self.image.horz_dpi
224239
height_in_emu = 914400 * px_height / horz_dpi
225240
return Emu(height_in_emu)
226241

@@ -243,6 +258,12 @@ def from_image(cls, image, partname):
243258
"""
244259
return ImagePart(partname, image.content_type, image.blob, image)
245260

261+
@property
262+
def image(self):
263+
if self._image is None:
264+
self._image = Image.from_blob(self.blob)
265+
return self._image
266+
246267
@classmethod
247268
def load(cls, partname, content_type, blob, package):
248269
"""

tests/parts/test_image.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,24 @@ def it_knows_its_default_dimensions_in_EMU(self, dimensions_fixture):
118118
def blob_(self, request):
119119
return instance_mock(request, str)
120120

121-
# param for one known from test_files at 72 dpi and created with from_image
122-
# param for one loaded by PartFactory with no Image instance
123-
@pytest.fixture
124-
def dimensions_fixture(self):
121+
@pytest.fixture(params=['loaded', 'new'])
122+
def dimensions_fixture(self, request):
125123
image_file_path = test_file('monty-truth.png')
126124
image = Image.load(image_file_path)
127-
image_part = ImagePart.from_image(image, None)
128-
cx, cy = 1905000, 2717800
129-
return image_part, cx, cy
125+
expected_cx, expected_cy = 1905000, 2717800
126+
127+
# case 1: image part is loaded by PartFactory w/no Image inst
128+
if request.param == 'loaded':
129+
partname = PackURI('/word/media/image1.png')
130+
content_type = CT.PNG
131+
image_part = ImagePart.load(
132+
partname, content_type, image.blob, None
133+
)
134+
# case 2: image part is newly created from image file
135+
elif request.param == 'new':
136+
image_part = ImagePart.from_image(image, None)
137+
138+
return image_part, expected_cx, expected_cy
130139

131140
@pytest.fixture
132141
def from_image_fixture(self, image_, partname_, ImagePart__init__):

0 commit comments

Comments
 (0)