diff --git a/test/test_wordcloud.py b/test/test_wordcloud.py index f3095040..6a5bb0cc 100644 --- a/test/test_wordcloud.py +++ b/test/test_wordcloud.py @@ -502,3 +502,22 @@ def test_max_font_size_as_mask_height(): # Check if the biggest element has the same font size assert wc.layout_[0][1] == wc2.layout_[0][1] + + +# test/test_wordcloud.py +def test_svg_contour(): + # Create a WordCloud with a contour + mask = np.ones((300, 300, 3), dtype=int) * 255 + mask[100:200, 100:200, :] = 0 + + wc = WordCloud(contour_width=1, contour_color='blue', mask=mask, background_color='white') + wc.generate(THIS) + + # Convert the WordCloud to SVG + svg_output = wc.to_svg() + + # Check that the SVG output includes the contour path + assert '>> WordCloud(color_func=colormap_color_func("magma")) """ + def __init__(self, colormap): import matplotlib.pyplot as plt self.colormap = plt.get_cmap(colormap) @@ -991,8 +992,23 @@ def to_svg(self, embed_font=False, optimize_embedded_font=True, embed_image=Fals ) ) - # TODO draw contour - + if self.mask is not None and self.contour_width > 0: + img = self.to_image() + contour = self._get_contour(img) + contour_path = self._get_svg_contour_path(contour) + result.append( + '' + .format( + contour_path, + self.contour_color, + self.contour_width + ) + ) # Complete SVG file result.append('') return '\n'.join(result) @@ -1012,27 +1028,7 @@ def _get_bolean_mask(self, mask): return boolean_mask def _draw_contour(self, img): - """Draw mask contour on a pillow image.""" - if self.mask is None or self.contour_width == 0: - return img - - mask = self._get_bolean_mask(self.mask) * 255 - contour = Image.fromarray(mask.astype(np.uint8)) - contour = contour.resize(img.size) - contour = contour.filter(ImageFilter.FIND_EDGES) - contour = np.array(contour) - - # make sure borders are not drawn before changing width - contour[[0, -1], :] = 0 - contour[:, [0, -1]] = 0 - - # use gaussian to change width, divide by 10 to give more resolution - radius = self.contour_width / 10 - contour = Image.fromarray(contour) - contour = contour.filter(ImageFilter.GaussianBlur(radius=radius)) - contour = np.array(contour) > 0 - contour = np.dstack((contour, contour, contour)) - + contour = self._get_contour(img) # color the contour ret = np.array(img) * np.invert(contour) if self.contour_color != 'black': @@ -1040,3 +1036,51 @@ def _draw_contour(self, img): ret += np.array(color) * contour return Image.fromarray(ret) + + def _get_contour(self, img): + """ + Get the contour of the mask. + + Args: + img (PIL.Image.Image): The input image. + + Returns: + numpy.ndarray: The contour of the mask as a binary array. + """ + if self.mask is None or self.contour_width == 0: + return None + mask = self._get_bolean_mask(self.mask) * 255 + contour = Image.fromarray(mask.astype(np.uint8)) + contour = contour.resize(img.size) + contour = contour.filter(ImageFilter.FIND_EDGES) + contour = np.array(contour) + + # make sure borders are not drawn before changing width + contour[[0, -1], :] = 0 + contour[:, [0, -1]] = 0 + + # use gaussian to change width, divide by 10 to give more resolution + radius = self.contour_width / 10 + contour = Image.fromarray(contour) + contour = contour.filter(ImageFilter.GaussianBlur(radius=radius)) + contour = np.array(contour) > 0 + contour = np.dstack((contour, contour, contour)) + + return contour + + def _get_svg_contour_path(self, contour): + """Get SVG path of contour. + + Args: + contour (ndarray): The contour array. + + Returns: + str: The SVG path string. + + """ + svg_path_str = '' + for i in range(contour.shape[0]): + for j in range(contour.shape[1]): + if contour[i, j, 0]: + svg_path_str += 'M{},{} h1 v1 h-1 Z '.format(j, i) + return svg_path_str