convert-stream 1.2.1__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of convert-stream might be problematic. Click here for more details.
- {convert_stream-1.2.1 → convert_stream-1.2.3}/PKG-INFO +1 -1
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/__main__.py +11 -8
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/_version.py +3 -3
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/convert.py +51 -9
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/imagelib.py +100 -60
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/models/m_text.py +2 -2
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/models/models_image.py +3 -1
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/models/models_pdf.py +96 -105
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/pdf_document.py +20 -13
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/pdf_page.py +74 -57
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/progress/progress_bar.py +8 -7
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/PKG-INFO +1 -1
- {convert_stream-1.2.1 → convert_stream-1.2.3}/pyproject.toml +1 -1
- {convert_stream-1.2.1 → convert_stream-1.2.3}/README.md +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/__init__.py +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/image_to_pdf.py +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/models/__init__.py +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/pdf_compress.py +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/pdf_to_images.py +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/progress/__init__.py +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/text.py +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/SOURCES.txt +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/dependency_links.txt +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/requires.txt +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/top_level.txt +0 -0
- {convert_stream-1.2.1 → convert_stream-1.2.3}/setup.cfg +0 -0
|
@@ -24,14 +24,17 @@ from convert_stream import (
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def test():
|
|
27
|
-
from convert_stream import
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
from convert_stream import (
|
|
28
|
+
FileDocumentPdf, CompressDocument, DocumentPdf, LibraryPDF,
|
|
29
|
+
PdfStream, ImageStream, PageDocumentPdf,
|
|
30
|
+
)
|
|
31
|
+
from soup_files import (
|
|
32
|
+
Directory, File, UserFileSystem, InputFiles
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
outdir: Directory = UserFileSystem().userDownloads.concat('output', create=True)
|
|
36
|
+
indir: Directory = UserFileSystem().userDownloads.concat('TESTE-PDF', create=True)
|
|
37
|
+
base = indir.join_file('base.pdf')
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def main():
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
__version__ = '1.2.
|
|
3
|
+
__version__ = '1.2.3'
|
|
4
4
|
__author__ = 'Bruno Chaves'
|
|
5
5
|
__license__ = 'MIT'
|
|
6
|
-
__module_name__ = '
|
|
7
|
-
__modify_date__ = '2025-06-
|
|
6
|
+
__module_name__ = 'convert_stream'
|
|
7
|
+
__modify_date__ = '2025-06-28'
|
|
@@ -81,12 +81,14 @@ class PdfStream(ABCDocumentStream):
|
|
|
81
81
|
print(f'{__class__.__name__} Erro: Use lista de PageDocumentPdf, não {type(pages)}')
|
|
82
82
|
return
|
|
83
83
|
max_num: int = len(pages)
|
|
84
|
+
self.progress_bar.start()
|
|
84
85
|
for num, p in enumerate(pages):
|
|
85
86
|
self.progress_bar.update(
|
|
86
87
|
((num+1)/max_num) * 100,
|
|
87
88
|
f'Adicionando página: [{num+1} de {max_num}]'
|
|
88
89
|
)
|
|
89
90
|
self.add_page(p)
|
|
91
|
+
self.progress_bar.stop()
|
|
90
92
|
|
|
91
93
|
def add_page_pdf_bytes(self, bt: bytes):
|
|
92
94
|
_page = PageDocumentPdf.create_from_page_bytes(bt, library=self.library_pdf)
|
|
@@ -107,6 +109,17 @@ class PdfStream(ABCDocumentStream):
|
|
|
107
109
|
def add_image(self, image: ImageObject):
|
|
108
110
|
pg: PageDocumentPdf = self.convert_images_to_pdf.from_image(image)
|
|
109
111
|
self.add_page(pg)
|
|
112
|
+
|
|
113
|
+
def add_images(self, images: List[ImageObject]):
|
|
114
|
+
maxnum: int = len(images)
|
|
115
|
+
self.progress_bar.start()
|
|
116
|
+
for n, img in enumerate(images):
|
|
117
|
+
self.progress_bar.update(
|
|
118
|
+
((n+1)/maxnum) * 100,
|
|
119
|
+
f'Adicionando imagens ao documento: [{n+1}] de {maxnum}'
|
|
120
|
+
)
|
|
121
|
+
self.add_image(img)
|
|
122
|
+
self.progress_bar.stop()
|
|
110
123
|
|
|
111
124
|
def to_file_pdf(self, f: File):
|
|
112
125
|
if self.is_null():
|
|
@@ -196,22 +209,25 @@ class PdfStream(ABCDocumentStream):
|
|
|
196
209
|
class ImageStream(object):
|
|
197
210
|
def __init__(
|
|
198
211
|
self, *,
|
|
199
|
-
library_image = LibraryImage.OPENCV,
|
|
200
|
-
library_pdf = LibraryPDF.PILPDF,
|
|
212
|
+
library_image: LibraryImage = LibraryImage.OPENCV,
|
|
213
|
+
library_pdf: LibraryPDF = LibraryPDF.PILPDF,
|
|
201
214
|
progress_bar: ProgressBarAdapter = ProgressBarAdapter(ProgressBarSimple()),
|
|
202
215
|
):
|
|
203
216
|
self.__images: List[ImageObject] = []
|
|
204
217
|
self.num_images: int = len(self.__images)
|
|
205
|
-
self.progress_bar: ProgressBarAdapter = progress_bar
|
|
218
|
+
#self.progress_bar: ProgressBarAdapter = progress_bar
|
|
206
219
|
self.library_image: LibraryImage = library_image
|
|
207
220
|
self.library_pdf: LibraryPDF = library_pdf
|
|
221
|
+
|
|
208
222
|
# Objeto para converter IMAGEM em pdf.
|
|
209
223
|
if library_pdf == LibraryPDF.FITZ:
|
|
210
|
-
self.convert_image_to_pdf:ImageConvertPdf = ImageConvertPdf.create_from_fitz(library_image)
|
|
224
|
+
self.convert_image_to_pdf: ImageConvertPdf = ImageConvertPdf.create_from_fitz(library_image)
|
|
211
225
|
elif library_pdf == LibraryPDF.CANVAS:
|
|
212
226
|
self.convert_image_to_pdf: ImageConvertPdf = ImageConvertPdf.create_from_canvas(library_image)
|
|
213
227
|
elif library_pdf == LibraryPDF.PILPDF:
|
|
214
228
|
self.convert_image_to_pdf: ImageConvertPdf = ImageConvertPdf.create_from_pil(library_image)
|
|
229
|
+
else:
|
|
230
|
+
raise ValueError(f'PYPDF não pode converter imagens em PDF!!')
|
|
215
231
|
|
|
216
232
|
# Objeto para converter PDF em imagem
|
|
217
233
|
self.convert_pdf_to_images: ConvertPdfToImage = ConvertPdfToImage(
|
|
@@ -220,6 +236,10 @@ class ImageStream(object):
|
|
|
220
236
|
dpi=150,
|
|
221
237
|
progress_bar=progress_bar,
|
|
222
238
|
)
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def progress_bar(self) -> ProgressBarAdapter:
|
|
242
|
+
return self.convert_pdf_to_images.pbar
|
|
223
243
|
|
|
224
244
|
@property
|
|
225
245
|
def images(self) -> List[ImageObject]:
|
|
@@ -237,20 +257,33 @@ class ImageStream(object):
|
|
|
237
257
|
def add_image(self, img: ImageObject) -> None:
|
|
238
258
|
if not isinstance(img, ImageObject):
|
|
239
259
|
return
|
|
240
|
-
print(f'{__class__.__name__} Adicionando imagem {self.num_images+1}')
|
|
241
260
|
self.__images.append(img)
|
|
242
261
|
self.num_images += 1
|
|
243
262
|
|
|
244
263
|
def add_images(self, images: List[ImageObject]):
|
|
245
|
-
|
|
264
|
+
maxnum: int = len(images)
|
|
265
|
+
self.progress_bar.start()
|
|
266
|
+
for num, i in enumerate(images):
|
|
267
|
+
self.progress_bar.update(
|
|
268
|
+
((num+1)/maxnum) * 100,
|
|
269
|
+
f'Adicionando imagens: [{num+1} de {maxnum}]'
|
|
270
|
+
)
|
|
246
271
|
self.add_image(i)
|
|
272
|
+
self.progress_bar.stop()
|
|
247
273
|
|
|
248
|
-
def add_file_image(self, f:File):
|
|
274
|
+
def add_file_image(self, f: File):
|
|
249
275
|
img = ImageObject.create_from_file(f, library=self.library_image)
|
|
250
276
|
self.add_image(img)
|
|
251
277
|
|
|
252
278
|
def add_files_image(self, files: List[File]):
|
|
253
|
-
|
|
279
|
+
maxnum: int = len(files)
|
|
280
|
+
for num, f in enumerate(files):
|
|
281
|
+
p = ((num + 1) / maxnum) * 100
|
|
282
|
+
#print(p)
|
|
283
|
+
self.progress_bar.update(
|
|
284
|
+
p,
|
|
285
|
+
f'Adicionando arquivos: [{num + 1} de {maxnum}]'
|
|
286
|
+
)
|
|
254
287
|
self.add_file_image(f)
|
|
255
288
|
|
|
256
289
|
def is_null(self) -> bool:
|
|
@@ -264,6 +297,7 @@ class ImageStream(object):
|
|
|
264
297
|
|
|
265
298
|
def to_files_image(self, d: Directory, prefix: str = 'imagem'):
|
|
266
299
|
d.mkdir()
|
|
300
|
+
self.progress_bar.start()
|
|
267
301
|
for num, image in enumerate(self.images):
|
|
268
302
|
filename = f'{prefix}-{num+1}.png'
|
|
269
303
|
self.progress_bar.update(
|
|
@@ -271,14 +305,22 @@ class ImageStream(object):
|
|
|
271
305
|
f'Exportando imagem: {num+1} de {self.num_images} [{filename}]'
|
|
272
306
|
)
|
|
273
307
|
image.to_file(d.join_file(filename))
|
|
308
|
+
self.progress_bar.stop()
|
|
274
309
|
|
|
275
310
|
def to_pages_pdf(self) -> List[PageDocumentPdf]:
|
|
276
311
|
if self.is_null():
|
|
277
312
|
print(f'{__class__.__name__} Adicione imagens para prosseguir!')
|
|
278
313
|
return []
|
|
279
314
|
new_pages = []
|
|
280
|
-
|
|
315
|
+
maxnum: int = len(self.images)
|
|
316
|
+
self.progress_bar.start()
|
|
317
|
+
for num, image in enumerate(self.images):
|
|
318
|
+
self.progress_bar.update(
|
|
319
|
+
((num + 1) / maxnum) * 100,
|
|
320
|
+
f'Adicionando imagens: [{num + 1} de {maxnum}]'
|
|
321
|
+
)
|
|
281
322
|
new_pages.append(self.convert_image_to_pdf.from_image(image))
|
|
323
|
+
self.progress_bar.stop()
|
|
282
324
|
return new_pages
|
|
283
325
|
|
|
284
326
|
def set_paisagem(self):
|
|
@@ -7,41 +7,48 @@ from PIL import Image, ImageOps, ImageFilter
|
|
|
7
7
|
from cv2.typing import MatLike
|
|
8
8
|
import cv2
|
|
9
9
|
import numpy as np
|
|
10
|
-
|
|
11
10
|
from soup_files import File
|
|
12
11
|
from convert_stream.models.models_image import LibraryImage, ABCImageObject, ABCInvertColor
|
|
13
12
|
|
|
14
13
|
#=============================================================================#
|
|
15
14
|
# Inverter cores em imagens
|
|
16
15
|
#=============================================================================#
|
|
16
|
+
|
|
17
|
+
|
|
17
18
|
class ImplementInvertColorOpenCv(ABCInvertColor):
|
|
18
19
|
"""
|
|
19
20
|
Escurecer texto em imagens.
|
|
20
21
|
"""
|
|
21
|
-
def __init__(self, image_bytes):
|
|
22
|
+
def __init__(self, image_bytes: bytes):
|
|
22
23
|
super().__init__()
|
|
23
24
|
if not isinstance(image_bytes, bytes):
|
|
24
25
|
raise ValueError(f'{__class__.__name__}\nUse: bytes, não {type(image_bytes)}')
|
|
25
|
-
self.image_bytes:bytes = image_bytes
|
|
26
|
+
self.image_bytes: bytes = image_bytes
|
|
26
27
|
|
|
27
|
-
def _image_bytes_to_opencv(self, img_bytes:bytes) -> MatLike:
|
|
28
|
+
def _image_bytes_to_opencv(self, img_bytes: bytes) -> MatLike:
|
|
28
29
|
"""Converte os bytes de uma imagem em objeto opencv MatLike"""
|
|
29
30
|
nparr = np.frombuffer(img_bytes, np.uint8)
|
|
30
31
|
return cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)
|
|
31
32
|
|
|
32
33
|
def _image_opencv_tobytes(self, img:MatLike) -> bytes:
|
|
33
34
|
"""Convert um objeto opencv MatLike em bytes de imagem"""
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
# Codifica como PNG (ou use '.jpg' para JPEG)
|
|
36
|
+
_, buffer = cv2.imencode('.png', img)
|
|
37
|
+
# Obtém os bytes da imagem
|
|
38
|
+
return buffer.tobytes()
|
|
36
39
|
|
|
37
40
|
def set_gaussian_blur(self):
|
|
38
41
|
# Aplica um filtro Gaussiano para reduzir o ruído
|
|
39
|
-
_blurred:MatLike = cv2.GaussianBlur(
|
|
42
|
+
_blurred: MatLike = cv2.GaussianBlur(
|
|
43
|
+
self._image_bytes_to_opencv(self.image_bytes),
|
|
44
|
+
(3, 3),
|
|
45
|
+
0.5
|
|
46
|
+
)
|
|
40
47
|
self.image_bytes = self._image_opencv_tobytes(_blurred)
|
|
41
48
|
|
|
42
|
-
def
|
|
49
|
+
def __set_background_black(self):
|
|
43
50
|
# Aplica binarização adaptativa (texto branco, fundo preto)
|
|
44
|
-
binary:MatLike = cv2.adaptiveThreshold(
|
|
51
|
+
binary: MatLike = cv2.adaptiveThreshold(
|
|
45
52
|
self._image_bytes_to_opencv(self.image_bytes),
|
|
46
53
|
150,
|
|
47
54
|
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
|
@@ -51,7 +58,7 @@ class ImplementInvertColorOpenCv(ABCInvertColor):
|
|
|
51
58
|
)
|
|
52
59
|
self.image_bytes = self._image_opencv_tobytes(binary)
|
|
53
60
|
|
|
54
|
-
def
|
|
61
|
+
def __set_background_gray(self):
|
|
55
62
|
# Aplica binarização adaptativa (texto preto, fundo branco)
|
|
56
63
|
_binary = cv2.adaptiveThreshold(
|
|
57
64
|
self._image_bytes_to_opencv(self.image_bytes),
|
|
@@ -65,17 +72,17 @@ class ImplementInvertColorOpenCv(ABCInvertColor):
|
|
|
65
72
|
|
|
66
73
|
def set_background_black(self):
|
|
67
74
|
self.set_gaussian_blur()
|
|
68
|
-
self.
|
|
69
|
-
_result:MatLike = cv2.bitwise_not(self._image_bytes_to_opencv(self.image_bytes))
|
|
75
|
+
self.__set_background_black()
|
|
76
|
+
_result: MatLike = cv2.bitwise_not(self._image_bytes_to_opencv(self.image_bytes))
|
|
70
77
|
self.image_bytes = self._image_opencv_tobytes(_result)
|
|
71
78
|
|
|
72
79
|
def set_background_gray(self):
|
|
73
80
|
self.set_gaussian_blur()
|
|
74
|
-
self.
|
|
75
|
-
_result:MatLike = cv2.bitwise_not(self._image_bytes_to_opencv(self.image_bytes))
|
|
81
|
+
self.__set_background_gray()
|
|
82
|
+
_result: MatLike = cv2.bitwise_not(self._image_bytes_to_opencv(self.image_bytes))
|
|
76
83
|
self.image_bytes = self._image_opencv_tobytes(_result)
|
|
77
84
|
|
|
78
|
-
def to_file(self, output_path:File):
|
|
85
|
+
def to_file(self, output_path: File):
|
|
79
86
|
print(f'Salvando: {output_path.basename()}')
|
|
80
87
|
cv2.imwrite(output_path.absolute(), self._image_bytes_to_opencv(self.image_bytes))
|
|
81
88
|
|
|
@@ -99,12 +106,12 @@ class ImplementInvertColorPIL(ABCInvertColor):
|
|
|
99
106
|
super().__init__()
|
|
100
107
|
if not isinstance(image_bytes, bytes):
|
|
101
108
|
raise ValueError(f'{__class__.__name__}\nUse: bytes, não {type(image_bytes)}')
|
|
102
|
-
self.image_bytes:bytes = image_bytes
|
|
109
|
+
self.image_bytes: bytes = image_bytes
|
|
103
110
|
|
|
104
111
|
def set_gaussian_blur(self):
|
|
105
112
|
img_pil = Image.open(BytesIO(self.image_bytes))
|
|
106
113
|
# Aplicar um desfoque semelhante ao cv2.GaussianBlur
|
|
107
|
-
blurred:Image.Image = img_pil.filter(ImageFilter.GaussianBlur(radius=1))
|
|
114
|
+
blurred: Image.Image = img_pil.filter(ImageFilter.GaussianBlur(radius=1))
|
|
108
115
|
buff = BytesIO()
|
|
109
116
|
blurred.save(buff, 'PNG')
|
|
110
117
|
self.image_bytes = buff.getvalue()
|
|
@@ -139,7 +146,7 @@ class ImplementInvertColorPIL(ABCInvertColor):
|
|
|
139
146
|
buff.close()
|
|
140
147
|
|
|
141
148
|
def to_file(self, output_path: File):
|
|
142
|
-
img:Image.Image = Image.open(self.image_bytes)
|
|
149
|
+
img: Image.Image = Image.open(self.image_bytes)
|
|
143
150
|
img.save(output_path.absolute(), 'PNG')
|
|
144
151
|
|
|
145
152
|
def to_bytes(self) -> bytes:
|
|
@@ -157,7 +164,7 @@ class ImageInverColor(ABCInvertColor):
|
|
|
157
164
|
"""
|
|
158
165
|
Escurecer texto em imagens.
|
|
159
166
|
"""
|
|
160
|
-
def __init__(self, invert_color:ABCInvertColor):
|
|
167
|
+
def __init__(self, invert_color: ABCInvertColor):
|
|
161
168
|
if not isinstance(invert_color, ABCInvertColor):
|
|
162
169
|
raise ValueError(f'{__class__.__name__}\nUse: ABCInvertColor(), não {type(invert_color)}')
|
|
163
170
|
self.invert_color:ABCInvertColor = invert_color
|
|
@@ -178,12 +185,15 @@ class ImageInverColor(ABCInvertColor):
|
|
|
178
185
|
return self.invert_color.to_bytes()
|
|
179
186
|
|
|
180
187
|
@classmethod
|
|
181
|
-
def create_from_file(
|
|
182
|
-
|
|
188
|
+
def create_from_file(
|
|
189
|
+
cls, f: File, *,
|
|
190
|
+
library: LibraryImage = LibraryImage.OPENCV
|
|
191
|
+
) -> ImageInverColor:
|
|
192
|
+
img: ImageObject = ImageObject.create_from_file(f, library=library)
|
|
183
193
|
if library == LibraryImage.OPENCV:
|
|
184
|
-
inv:ABCInvertColor = ImplementInvertColorOpenCv(f.path.read_bytes())
|
|
194
|
+
inv: ABCInvertColor = ImplementInvertColorOpenCv(f.path.read_bytes())
|
|
185
195
|
elif library == LibraryImage.PIL:
|
|
186
|
-
inv:ABCInvertColor = ImplementInvertColorPIL(f.path.read_bytes())
|
|
196
|
+
inv: ABCInvertColor = ImplementInvertColorPIL(f.path.read_bytes())
|
|
187
197
|
else:
|
|
188
198
|
raise NotImplementedError(
|
|
189
199
|
f'{__class__.__name__}\nUse: LibraryImage.OPENCV, ou LibraryImage.PIL, não {type(library)}'
|
|
@@ -191,49 +201,60 @@ class ImageInverColor(ABCInvertColor):
|
|
|
191
201
|
return cls(inv)
|
|
192
202
|
|
|
193
203
|
@classmethod
|
|
194
|
-
def create_from_bytes(
|
|
204
|
+
def create_from_bytes(
|
|
205
|
+
cls, bt: bytes, *,
|
|
206
|
+
library: LibraryImage = LibraryImage.OPENCV
|
|
207
|
+
) -> ImageInverColor:
|
|
195
208
|
if library == LibraryImage.OPENCV:
|
|
196
|
-
invert_color:ABCInvertColor = ImplementInvertColorOpenCv(bt)
|
|
209
|
+
invert_color: ABCInvertColor = ImplementInvertColorOpenCv(bt)
|
|
197
210
|
elif library == LibraryImage.PIL:
|
|
198
|
-
invert_color:ABCInvertColor = ImplementInvertColorPIL(bt)
|
|
211
|
+
invert_color: ABCInvertColor = ImplementInvertColorPIL(bt)
|
|
199
212
|
else:
|
|
200
213
|
raise NotImplementedError(
|
|
201
214
|
f'{__class__.__name__}\nUse: LibraryImage.OPENCV, ou LibraryImage.PIL, não {type(library)}'
|
|
202
215
|
)
|
|
203
216
|
return cls(invert_color)
|
|
204
217
|
|
|
218
|
+
|
|
205
219
|
#=============================================================================#
|
|
206
220
|
# Manipulação de imagens
|
|
207
221
|
#=============================================================================#
|
|
222
|
+
|
|
223
|
+
|
|
208
224
|
class ImageObjectPIL(ABCImageObject):
|
|
209
225
|
"""
|
|
210
226
|
Implementação de ImageObject usando PIL.
|
|
211
227
|
"""
|
|
212
|
-
def __init__(self, image_bytes:bytes):
|
|
228
|
+
def __init__(self, image_bytes: bytes):
|
|
213
229
|
super().__init__()
|
|
214
230
|
if not isinstance(image_bytes, bytes):
|
|
215
231
|
raise ValueError(f'{__class__.__name__} Use: bytes, não {type(image_bytes)}')
|
|
216
232
|
self.current_library = LibraryImage.PIL
|
|
217
233
|
self.image_bytes = image_bytes
|
|
218
|
-
self.max_size:Tuple[int, int] = (1980, 720) # Dimensões máximas, altere se necessário.
|
|
234
|
+
self.max_size: Tuple[int, int] = (1980, 720) # Dimensões máximas, altere se necessário.
|
|
219
235
|
|
|
220
236
|
try:
|
|
221
|
-
|
|
237
|
+
image_pil = Image.open(BytesIO(image_bytes))
|
|
222
238
|
except Exception as e:
|
|
223
239
|
raise ValueError(f"{__class__.__name__}\nPIL: Bytes de imagem inválidos")
|
|
224
240
|
else:
|
|
225
|
-
self.dimensions =
|
|
226
|
-
|
|
227
|
-
# Redimensionar, se as dimensões
|
|
228
|
-
if
|
|
229
|
-
#
|
|
230
|
-
buff_image:BytesIO = BytesIO()
|
|
231
|
-
|
|
241
|
+
self.dimensions = image_pil.size
|
|
242
|
+
|
|
243
|
+
# Redimensionar, se as dimensões estiverem maior que self.max_size.
|
|
244
|
+
if (image_pil.width > self.max_size[0]) or (image_pil.height > self.max_size[1]):
|
|
245
|
+
#print(f'{__class__.__name__} Redimensionando imagem [{self.dimensions}], aguarde...')
|
|
246
|
+
buff_image: BytesIO = BytesIO()
|
|
247
|
+
image_pil.save(buff_image, format='PNG', optimize=True, quality=80)
|
|
248
|
+
image_pil = Image.open(buff_image)
|
|
249
|
+
self.dimensions = image_pil.size
|
|
232
250
|
self.image_bytes = buff_image.getvalue()
|
|
233
|
-
self.dimensions = img.size
|
|
234
251
|
buff_image.seek(0)
|
|
235
252
|
buff_image.close()
|
|
236
|
-
|
|
253
|
+
del buff_image
|
|
254
|
+
#print(f'{__class__.__name__} Nova dimensão [{self.dimensions}]')
|
|
255
|
+
self.width = image_pil.width
|
|
256
|
+
self.height = image_pil.height
|
|
257
|
+
del image_pil
|
|
237
258
|
|
|
238
259
|
def is_paisagem(self) -> bool:
|
|
239
260
|
"""
|
|
@@ -264,10 +285,10 @@ class ImageObjectPIL(ABCImageObject):
|
|
|
264
285
|
img.save(new_bytes, format='PNG')
|
|
265
286
|
self.image_bytes = new_bytes.getvalue()
|
|
266
287
|
|
|
267
|
-
def set_optimize(self):
|
|
288
|
+
def set_optimize(self, quality: int = 70):
|
|
268
289
|
optimized_bytes = BytesIO()
|
|
269
290
|
img = self.to_image_pil()
|
|
270
|
-
img.save(optimized_bytes, format='PNG', optimize=True, quality=
|
|
291
|
+
img.save(optimized_bytes, format='PNG', optimize=True, quality=quality)
|
|
271
292
|
self.image_bytes = optimized_bytes.getvalue()
|
|
272
293
|
|
|
273
294
|
def set_background_black(self):
|
|
@@ -306,6 +327,7 @@ class ImageObjectPIL(ABCImageObject):
|
|
|
306
327
|
except Exception as e:
|
|
307
328
|
raise Exception(f"Erro ao salvar imagem PIL: {e}")
|
|
308
329
|
|
|
330
|
+
|
|
309
331
|
class ImageObjectOpenCV(ABCImageObject):
|
|
310
332
|
"""
|
|
311
333
|
Implementação de ImageObject usando OpenCV.
|
|
@@ -316,11 +338,11 @@ class ImageObjectOpenCV(ABCImageObject):
|
|
|
316
338
|
raise ValueError(f'{__class__.__name__} Use: bytes, não {type(image_bytes)}')
|
|
317
339
|
self.current_library = LibraryImage.OPENCV
|
|
318
340
|
self.image_bytes = image_bytes
|
|
319
|
-
self.max_size:Tuple[int, int] = (1980, 720)
|
|
341
|
+
self.max_size: Tuple[int, int] = (1980, 720)
|
|
320
342
|
|
|
321
343
|
try:
|
|
322
344
|
nparr = np.frombuffer(self.image_bytes, np.uint8)
|
|
323
|
-
image_opencv:MatLike = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
|
345
|
+
image_opencv: MatLike = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
|
324
346
|
except Exception as e:
|
|
325
347
|
print('-' * 80)
|
|
326
348
|
raise ValueError(f"\nCV2: Bytes de imagem OpenCV inválidos")
|
|
@@ -328,16 +350,20 @@ class ImageObjectOpenCV(ABCImageObject):
|
|
|
328
350
|
self.dimensions = (image_opencv.shape[1], image_opencv.shape[0]) # (largura, altura)
|
|
329
351
|
|
|
330
352
|
# Redimensionar se necessário
|
|
331
|
-
if self.dimensions[0] > self.max_size[0] or self.dimensions[1] > self.max_size[1]:
|
|
353
|
+
if (self.dimensions[0] > self.max_size[0]) or (self.dimensions[1] > self.max_size[1]):
|
|
354
|
+
#print(f'{__class__.__name__} Redimensionando imagem [{self.dimensions}], aguarde...')
|
|
332
355
|
h, w = image_opencv.shape[:2]
|
|
333
356
|
scale = min(self.max_size[0] / w, self.max_size[1] / h)
|
|
334
357
|
new_size = (int(w * scale), int(h * scale))
|
|
335
|
-
image_opencv = cv2.resize(image_opencv, new_size, interpolation=cv2.INTER_LANCZOS4)
|
|
358
|
+
image_opencv = cv2.resize(image_opencv, new_size, interpolation=cv2.INTER_LANCZOS4)
|
|
359
|
+
self.dimensions = (image_opencv.shape[1], image_opencv.shape[0])
|
|
360
|
+
#print(f'{__class__.__name__} Nova dimensão [{self.dimensions}]')
|
|
336
361
|
# Converter a imagem de volta para bytes
|
|
337
362
|
_, encoded_img = cv2.imencode('.png', image_opencv)
|
|
338
363
|
self.image_bytes = encoded_img.tobytes()
|
|
339
|
-
# Atualizar dimensões após
|
|
340
|
-
self.
|
|
364
|
+
# Atualizar dimensões após redução
|
|
365
|
+
self.height = self.dimensions[0]
|
|
366
|
+
self.width = self.dimensions[1]
|
|
341
367
|
|
|
342
368
|
def is_paisagem(self) -> bool:
|
|
343
369
|
"""
|
|
@@ -372,12 +398,12 @@ class ImageObjectOpenCV(ABCImageObject):
|
|
|
372
398
|
if success:
|
|
373
399
|
self.image_bytes = encoded_image.tobytes()
|
|
374
400
|
|
|
375
|
-
def set_optimize(self):
|
|
401
|
+
def set_optimize(self, quality: int = 70):
|
|
376
402
|
"""
|
|
377
403
|
Reduz o tamanho da imagem, e salva a imagen reduzida na propriedade bytes.
|
|
378
404
|
"""
|
|
379
|
-
imagem:MatLike = self.to_image_opencv()
|
|
380
|
-
_status, buffer = cv2.imencode(".png", imagem, [cv2.IMWRITE_JPEG_QUALITY,
|
|
405
|
+
imagem: MatLike = self.to_image_opencv()
|
|
406
|
+
_status, buffer = cv2.imencode(".png", imagem, [cv2.IMWRITE_JPEG_QUALITY, quality])
|
|
381
407
|
if _status:
|
|
382
408
|
self.image_bytes = buffer.tobytes()
|
|
383
409
|
|
|
@@ -416,20 +442,29 @@ class ImageObjectOpenCV(ABCImageObject):
|
|
|
416
442
|
except Exception as e:
|
|
417
443
|
raise Exception(f"Erro ao salvar imagem OpenCV: {e}")
|
|
418
444
|
|
|
419
|
-
|
|
445
|
+
|
|
446
|
+
class ImageObject(object):
|
|
420
447
|
"""
|
|
421
448
|
Facade para manipular imagens com PIL ou OPENCV
|
|
422
449
|
"""
|
|
423
|
-
def __init__(self, img_obj:ABCImageObject):
|
|
450
|
+
def __init__(self, img_obj: ABCImageObject):
|
|
424
451
|
super().__init__()
|
|
425
|
-
self.img_obj:ABCImageObject = img_obj
|
|
426
|
-
self.current_library:LibraryImage = self.img_obj.current_library
|
|
427
|
-
|
|
428
|
-
|
|
452
|
+
self.img_obj: ABCImageObject = img_obj
|
|
453
|
+
self.current_library: LibraryImage = self.img_obj.current_library
|
|
454
|
+
|
|
455
|
+
@property
|
|
456
|
+
def width(self) -> int:
|
|
457
|
+
return self.img_obj.width
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def height(self) -> int:
|
|
461
|
+
return self.img_obj.height
|
|
462
|
+
|
|
463
|
+
def set_optimize(self, quality: int = 70):
|
|
429
464
|
"""
|
|
430
465
|
Reduz o tamanho da imagem, e salva a imagen reduzida na propriedade bytes.
|
|
431
466
|
"""
|
|
432
|
-
self.img_obj.set_optimize()
|
|
467
|
+
self.img_obj.set_optimize(quality)
|
|
433
468
|
|
|
434
469
|
def set_rotation(self, rotation: LibraryImage = LibraryImage.ROTATION_180):
|
|
435
470
|
self.img_obj.set_rotation(rotation)
|
|
@@ -452,7 +487,7 @@ class ImageObject(ABCImageObject):
|
|
|
452
487
|
def to_image_opencv(self) -> cv2.typing.MatLike:
|
|
453
488
|
return self.img_obj.to_image_opencv()
|
|
454
489
|
|
|
455
|
-
def to_file(self, f:File):
|
|
490
|
+
def to_file(self, f: File):
|
|
456
491
|
self.img_obj.to_file(f.absolute())
|
|
457
492
|
|
|
458
493
|
def is_paisagem(self) -> bool:
|
|
@@ -462,7 +497,10 @@ class ImageObject(ABCImageObject):
|
|
|
462
497
|
return self.img_obj.to_bytes()
|
|
463
498
|
|
|
464
499
|
@classmethod
|
|
465
|
-
def create_from_bytes(
|
|
500
|
+
def create_from_bytes(
|
|
501
|
+
cls, image_bytes: bytes, *,
|
|
502
|
+
library: LibraryImage = LibraryImage.OPENCV
|
|
503
|
+
) -> ImageObject:
|
|
466
504
|
if library == LibraryImage.PIL:
|
|
467
505
|
img = ImageObjectPIL(image_bytes)
|
|
468
506
|
elif library == LibraryImage.OPENCV:
|
|
@@ -472,8 +510,10 @@ class ImageObject(ABCImageObject):
|
|
|
472
510
|
return cls(img)
|
|
473
511
|
|
|
474
512
|
@classmethod
|
|
475
|
-
def create_from_file(
|
|
476
|
-
|
|
513
|
+
def create_from_file(
|
|
514
|
+
cls, filepath: File, *,
|
|
515
|
+
library: LibraryImage = LibraryImage.OPENCV
|
|
516
|
+
) -> ImageObject:
|
|
477
517
|
try:
|
|
478
518
|
if library == LibraryImage.PIL:
|
|
479
519
|
image = ImageObjectPIL(filepath.path.read_bytes())
|
|
@@ -371,7 +371,7 @@ class DataString(object):
|
|
|
371
371
|
|
|
372
372
|
|
|
373
373
|
class ArrayString(object):
|
|
374
|
-
def __init__(self, values:List[str]):
|
|
374
|
+
def __init__(self, values: List[str]):
|
|
375
375
|
self.values = values
|
|
376
376
|
self.length = len(self.values)
|
|
377
377
|
|
|
@@ -381,7 +381,7 @@ class ArrayString(object):
|
|
|
381
381
|
self.values.append(item)
|
|
382
382
|
self.length += 1
|
|
383
383
|
|
|
384
|
-
def add_all(self, items:List[str]):
|
|
384
|
+
def add_all(self, items: List[str]):
|
|
385
385
|
for i in items:
|
|
386
386
|
self.add(i)
|
|
387
387
|
|
|
@@ -47,6 +47,8 @@ class ABCImageObject(ABC):
|
|
|
47
47
|
def __init__(self):
|
|
48
48
|
super().__init__()
|
|
49
49
|
self.current_library: LibraryImage = None
|
|
50
|
+
self.width: int = 0
|
|
51
|
+
self.height: int = 0
|
|
50
52
|
|
|
51
53
|
@abstractmethod
|
|
52
54
|
def is_paisagem(self) -> bool:
|
|
@@ -56,7 +58,7 @@ class ABCImageObject(ABC):
|
|
|
56
58
|
pass
|
|
57
59
|
|
|
58
60
|
@abstractmethod
|
|
59
|
-
def set_optimize(self):
|
|
61
|
+
def set_optimize(self, quality: int = 70):
|
|
60
62
|
pass
|
|
61
63
|
|
|
62
64
|
@abstractmethod
|