Upload 2 files
Browse files- Sid_Dithers.py +694 -0
- weather_icon.bmp +0 -0
Sid_Dithers.py
ADDED
@@ -0,0 +1,694 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
# =======================
|
3 |
+
# Imports
|
4 |
+
# =======================
|
5 |
+
|
6 |
+
import os, sys, time, re
|
7 |
+
from datetime import datetime as dt
|
8 |
+
from itertools import chain, filterfalse
|
9 |
+
|
10 |
+
from urllib.request import urlopen
|
11 |
+
from urllib.request import Request
|
12 |
+
import urllib.error
|
13 |
+
|
14 |
+
import numpy as np
|
15 |
+
from PIL import Image
|
16 |
+
|
17 |
+
|
18 |
+
# =======================
|
19 |
+
# Functions
|
20 |
+
# =======================
|
21 |
+
|
22 |
+
# Internet Functions
|
23 |
+
|
24 |
+
def check_site_redirected(starturl):
|
25 |
+
|
26 |
+
res = urllib.request.urlopen(starturl)
|
27 |
+
finalurl = res.geturl()
|
28 |
+
return finalurl
|
29 |
+
|
30 |
+
def get_links(start_html, c_regex):
|
31 |
+
|
32 |
+
html_links = []
|
33 |
+
|
34 |
+
try:
|
35 |
+
# Spoof Header to look like regular user browser
|
36 |
+
req = Request(start_html)
|
37 |
+
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 AOL/9.8 AOLBuild/4346.2019.US Safari/537.36')
|
38 |
+
start_file = urlopen(req)
|
39 |
+
|
40 |
+
except urllib.error.HTTPError as e:
|
41 |
+
print('Download Failed for',start_html)
|
42 |
+
print('Status', e.code,' - Reason', e.reason,' - Url', e.url)
|
43 |
+
return html_links
|
44 |
+
|
45 |
+
start_text = str(start_file.read(),encoding="utf-8",errors="ignore") # is there an encoding problem -- eg utf-8 -- or errors="replace" - replaces the unencodable unicode to a question mark ?
|
46 |
+
start_file.close()
|
47 |
+
|
48 |
+
#print("DEBUG - sleep on link download")
|
49 |
+
#time.sleep(0.5) # DEBUG
|
50 |
+
|
51 |
+
if start_text != "": # If content exists
|
52 |
+
html_links = c_regex.findall(start_text)
|
53 |
+
|
54 |
+
return html_links
|
55 |
+
|
56 |
+
# Data Pipe functions
|
57 |
+
|
58 |
+
def pipe_inspector(link_input):
|
59 |
+
print("'",link_input,"',")
|
60 |
+
yield link_input
|
61 |
+
|
62 |
+
# ----------------
|
63 |
+
|
64 |
+
def link_from_list(link_list):
|
65 |
+
for link_input in link_list:
|
66 |
+
yield link_input
|
67 |
+
|
68 |
+
def get_test_links(link_input, c_regex):
|
69 |
+
html_links = c_regex.findall(str(link_input))
|
70 |
+
return html_links
|
71 |
+
|
72 |
+
def link_text_replace_filter(file_link):
|
73 |
+
return [file_link.replace("vids","highres")]
|
74 |
+
|
75 |
+
def link_prefix_adder(file_link):
|
76 |
+
return [prefix_adder+file_link]
|
77 |
+
|
78 |
+
|
79 |
+
def file_name_filter(file_link,c_regex):
|
80 |
+
file_name = file_link[file_link.rfind("/")+1:]
|
81 |
+
file_name = file_name[:-4]
|
82 |
+
#print(file_name)
|
83 |
+
link_filter = c_regex.findall(str(file_name))
|
84 |
+
if link_filter == []:
|
85 |
+
return ""
|
86 |
+
else:
|
87 |
+
return [file_link]
|
88 |
+
|
89 |
+
def file_name_generator(file_link):
|
90 |
+
|
91 |
+
str_template = "%2d"
|
92 |
+
file_list = []
|
93 |
+
|
94 |
+
for c in range(1,10+1):
|
95 |
+
file_name = str_template % (c)
|
96 |
+
file_list.append(file_name)
|
97 |
+
|
98 |
+
return file_list
|
99 |
+
|
100 |
+
|
101 |
+
# ========================
|
102 |
+
|
103 |
+
class link_prefix():
|
104 |
+
|
105 |
+
def __init__(self, file_prefix):
|
106 |
+
self.file_prefix = file_prefix
|
107 |
+
return
|
108 |
+
|
109 |
+
def add(self, file_link):
|
110 |
+
#print("'" + self.file_prefix + file_link + "',")
|
111 |
+
return [self.file_prefix + file_link]
|
112 |
+
|
113 |
+
class link_two_string():
|
114 |
+
|
115 |
+
def __init__(self, string_format, func1=None, func2=None):
|
116 |
+
self.string_format = string_format
|
117 |
+
self.func1 = func1
|
118 |
+
self.func2 = func2
|
119 |
+
return
|
120 |
+
|
121 |
+
def add(self, file_link):
|
122 |
+
|
123 |
+
if (self.func1 == None and self.func2 != None):
|
124 |
+
return [self.string_format % (file_link,self.func2(file_link))]
|
125 |
+
elif (self.func1!=None and self.func2==None):
|
126 |
+
return [self.string_format % (self.func1(file_link),file_link)]
|
127 |
+
elif (self.func1!=None and self.func2!=None):
|
128 |
+
return [self.string_format % (self.func1(file_link),self.func2(file_link))]
|
129 |
+
else:
|
130 |
+
return [self.string_format % (file_link,file_link)]
|
131 |
+
|
132 |
+
class link_generator():
|
133 |
+
|
134 |
+
def __init__(self, file_ext,header,start,end):
|
135 |
+
self.file_ext = file_ext
|
136 |
+
self.header = header
|
137 |
+
self.start = start
|
138 |
+
self.end = end
|
139 |
+
self.filelist = []
|
140 |
+
return
|
141 |
+
|
142 |
+
def add(self, file_link):
|
143 |
+
self.filelist = []
|
144 |
+
for c in range(self.start,self.end+1):
|
145 |
+
#filename = file_link + "/" + self.header % (c) + self.file_ext
|
146 |
+
filename = file_link + self.header % (c) + self.file_ext
|
147 |
+
self.filelist.append(filename)
|
148 |
+
return self.filelist
|
149 |
+
|
150 |
+
# Iterator Functions
|
151 |
+
|
152 |
+
def html_cal_iter(html_str,start_year,end_year,end_month):
|
153 |
+
for tgt_year in range(start_year, end_year+1):
|
154 |
+
for tgt_month in range(1,12+1):
|
155 |
+
if tgt_year < end_year or (tgt_year == end_year and tgt_month <= end_month):
|
156 |
+
yield html_str % (tgt_year,tgt_month)
|
157 |
+
|
158 |
+
def html_num_iter(html_str,start_page,end_page):
|
159 |
+
for tgt_page in range(start_page, end_page+1):
|
160 |
+
yield html_str % (tgt_page)
|
161 |
+
|
162 |
+
def html_list_iter(iter_list):
|
163 |
+
for tgt_page in iter_list:
|
164 |
+
yield tgt_page
|
165 |
+
|
166 |
+
def html_alpha_iter(html_str,start_letter=0,end_letter=26,use_upper=True):
|
167 |
+
|
168 |
+
alpha = "abcdefghijklmnopqrstuvwxyz"
|
169 |
+
alpha = alpha[start_letter:end_letter]
|
170 |
+
|
171 |
+
if use_upper:
|
172 |
+
alpha = alpha.upper()
|
173 |
+
|
174 |
+
for tgt_page in alpha:
|
175 |
+
yield html_str % (tgt_page)
|
176 |
+
|
177 |
+
|
178 |
+
# =========================
|
179 |
+
# Classes
|
180 |
+
# =========================
|
181 |
+
|
182 |
+
# Wrapper classes used for data pipes
|
183 |
+
|
184 |
+
class cDownloader:
|
185 |
+
|
186 |
+
def __init__(self, file_namer, file_extension, file_prefix="", file_start_counter=0, pause=0.2, dead_link_kill=3, snap_page=False):
|
187 |
+
|
188 |
+
# Parameters to set for processing by the data pipes
|
189 |
+
|
190 |
+
self.file_namer = file_namer
|
191 |
+
self.file_ext = file_extension
|
192 |
+
self.prefix = file_prefix
|
193 |
+
#self.html_level_prefix = html_level_prefix
|
194 |
+
self.file_count = file_start_counter
|
195 |
+
self.pause = pause
|
196 |
+
|
197 |
+
self.dead_link_kill = dead_link_kill
|
198 |
+
self.dead_links = 0
|
199 |
+
self.last_dead_link = ""
|
200 |
+
self.snap_page = snap_page
|
201 |
+
|
202 |
+
if self.snap_page:
|
203 |
+
|
204 |
+
options = webdriver.ChromeOptions()
|
205 |
+
options.add_argument("headless")
|
206 |
+
options.add_argument("window-size=2500,1200")
|
207 |
+
|
208 |
+
self.browser = webdriver.Chrome(chrome_options=options)
|
209 |
+
|
210 |
+
def __del__(self):
|
211 |
+
|
212 |
+
if self.snap_page:
|
213 |
+
self.browser.quit() # close() ?
|
214 |
+
|
215 |
+
def download(self, file_link): # This gets called externally by the data pipes
|
216 |
+
|
217 |
+
if file_link is None:
|
218 |
+
pass
|
219 |
+
|
220 |
+
else:
|
221 |
+
|
222 |
+
self.file_count += 1
|
223 |
+
add_prefix = ""
|
224 |
+
|
225 |
+
if self.file_namer == "FILENAME":
|
226 |
+
|
227 |
+
trim_file_link = file_link[:file_link.rfind(self.file_ext)+len(self.file_ext)] # Clip name if filelink has parameters after the filename
|
228 |
+
file_name = self.prefix + add_prefix + trim_file_link[trim_file_link.rfind("/")+1:] # Extract file name from link
|
229 |
+
|
230 |
+
else: # defaults to file counter
|
231 |
+
file_name = (self.prefix + add_prefix + "%06d" + self.file_ext) % self.file_count
|
232 |
+
|
233 |
+
# Attempt to download the file but check if we have exceeded the dead link count
|
234 |
+
|
235 |
+
if (self.dead_links >= self.dead_link_kill and self.last_dead_link == file_link[:file_link.rfind("/")]):
|
236 |
+
|
237 |
+
#print('ERROR: cDownloader - REASON: Exceeded dead links')
|
238 |
+
pass
|
239 |
+
|
240 |
+
elif os.path.exists(file_name):
|
241 |
+
print("WARNING: File exists ",file_name," skipping")
|
242 |
+
pass
|
243 |
+
|
244 |
+
|
245 |
+
# Begin download
|
246 |
+
|
247 |
+
if self.snap_page: # Take a snapshot of the webpage instead of downloading a file
|
248 |
+
|
249 |
+
self.browser.get(file_link)
|
250 |
+
self.browser.save_screenshot(file_name+".png") # always a png ext
|
251 |
+
time.sleep(self.pause)
|
252 |
+
|
253 |
+
else: # download the file
|
254 |
+
|
255 |
+
try:
|
256 |
+
req = Request(file_link)
|
257 |
+
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 AOL/9.8 AOLBuild/4346.2019.US Safari/537.36')
|
258 |
+
remotefile = urlopen(req)
|
259 |
+
|
260 |
+
localfile = open(file_name, 'wb')
|
261 |
+
localfile.write(remotefile.read())
|
262 |
+
localfile.close()
|
263 |
+
remotefile.close()
|
264 |
+
|
265 |
+
#print("DEBUG - sleep on file download")
|
266 |
+
#time.sleep(0.5) # DEBUG
|
267 |
+
|
268 |
+
self.dead_links = 0 # successful download, reset dead link count to zero
|
269 |
+
self.last_dead_link = ""
|
270 |
+
|
271 |
+
except urllib.error.HTTPError as e:
|
272 |
+
|
273 |
+
print('ERROR: ',e.code,' REASON: ',e.reason, " => ",file_link) # Url = e.url
|
274 |
+
|
275 |
+
self.dead_links += 1 # unsuccessful download, add dead link data
|
276 |
+
self.last_dead_link = file_link[:file_link.rfind("/")] # root of file attempted
|
277 |
+
|
278 |
+
|
279 |
+
|
280 |
+
## END class cDownloader
|
281 |
+
|
282 |
+
|
283 |
+
# Data Pipe Class
|
284 |
+
|
285 |
+
class cDataPipe:
|
286 |
+
|
287 |
+
def __init__(self):
|
288 |
+
|
289 |
+
self.nLayers = 0
|
290 |
+
self.DataPipes = []
|
291 |
+
|
292 |
+
def new_pipe(self, data_pump):
|
293 |
+
self.DataPipes.append([data_input for data_input in data_pump])
|
294 |
+
|
295 |
+
def end_pipe(self, data_downloader):
|
296 |
+
self.nLayers += 1
|
297 |
+
self.DataPipes.append([data_downloader(data_input) for data_input in self.DataPipes[self.nLayers-1]])
|
298 |
+
|
299 |
+
def add_pipe(self, data_processor, c_regex=""):
|
300 |
+
|
301 |
+
self.nLayers += 1
|
302 |
+
|
303 |
+
if c_regex == "":
|
304 |
+
self.DataPipes.append(self.unique_items(self.flatten([data_processor(data_input) for data_input in self.DataPipes[self.nLayers-1]])))
|
305 |
+
else:
|
306 |
+
self.DataPipes.append(self.unique_items(self.flatten([data_processor(data_input,c_regex) for data_input in self.DataPipes[self.nLayers-1]])))
|
307 |
+
|
308 |
+
def output(self):
|
309 |
+
return list(self.DataPipes[self.nLayers])
|
310 |
+
|
311 |
+
def flow(self):
|
312 |
+
yield self.DataPipes[self.nLayers]
|
313 |
+
|
314 |
+
def flatten(self, list_of_lists):
|
315 |
+
|
316 |
+
flat_list = []
|
317 |
+
|
318 |
+
try:
|
319 |
+
flat_list = list(chain.from_iterable(list_of_lists)) # Note -- this will fail if any item is not an iterable - oth params ??
|
320 |
+
except:
|
321 |
+
print("ERROR - Non-iterable in flatten chain")
|
322 |
+
return []
|
323 |
+
|
324 |
+
return flat_list
|
325 |
+
|
326 |
+
def unique_items(self, iterable):
|
327 |
+
|
328 |
+
seen = set()
|
329 |
+
|
330 |
+
for element in filterfalse(seen.__contains__,iterable):
|
331 |
+
seen.add(element)
|
332 |
+
yield element
|
333 |
+
|
334 |
+
|
335 |
+
# ===================
|
336 |
+
# Image Processing Functions
|
337 |
+
# ===================
|
338 |
+
|
339 |
+
|
340 |
+
|
341 |
+
|
342 |
+
'''
|
343 |
+
|
344 |
+
Floyd-Steinberg Dithering Example
|
345 |
+
https://scipython.com/blog/floyd-steinberg-dithering/
|
346 |
+
|
347 |
+
Also see:
|
348 |
+
|
349 |
+
https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html
|
350 |
+
https://en.wikipedia.org/wiki/Ordered_dithering
|
351 |
+
https://archive.ph/71e9G
|
352 |
+
|
353 |
+
Adjusted for ePaper colours - which are 7
|
354 |
+
|
355 |
+
Supported Colours
|
356 |
+
|
357 |
+
Brown R - 255 G - 128 B - 0
|
358 |
+
Green R - 0 G - 255 B - 0
|
359 |
+
White R - 255 G - 255 B - 255
|
360 |
+
Black R - 0 G - 0 B - 0
|
361 |
+
Red R - 255 G -0 B - 0
|
362 |
+
Blue R - 0 G - 0 B - 255
|
363 |
+
Yellow R - 255 G 255 B - 0
|
364 |
+
|
365 |
+
Not Supported (but would be normal 2 channel colours)
|
366 |
+
|
367 |
+
Purple R - 255 G 0 B - 255
|
368 |
+
Turq R - 0 G 255 B - 255
|
369 |
+
|
370 |
+
'''
|
371 |
+
|
372 |
+
|
373 |
+
|
374 |
+
# =================================
|
375 |
+
# Constants
|
376 |
+
# =================================
|
377 |
+
|
378 |
+
|
379 |
+
|
380 |
+
colour_map = [np.array([0,0,0]), # Black
|
381 |
+
np.array([255,0,0]), # Red
|
382 |
+
np.array([0,255,0]), # Green
|
383 |
+
np.array([0,0,255]), # Blue
|
384 |
+
np.array([255,255,0]), # Yellow
|
385 |
+
np.array([255,128,0]), # Brown
|
386 |
+
np.array([255,255,255]), # White
|
387 |
+
]
|
388 |
+
|
389 |
+
NUMBER_COLOURS = len(colour_map)
|
390 |
+
|
391 |
+
|
392 |
+
# =================================
|
393 |
+
# FUNCTIONS
|
394 |
+
# =================================
|
395 |
+
|
396 |
+
|
397 |
+
def get_new_val(old_val, nc):
|
398 |
+
"""
|
399 |
+
Get the "closest" colour to old_val in the range [0,1] per channel divided
|
400 |
+
into nc values.
|
401 |
+
|
402 |
+
"""
|
403 |
+
|
404 |
+
return np.round(old_val * (nc - 1)) / (nc - 1)
|
405 |
+
|
406 |
+
'''
|
407 |
+
|
408 |
+
Floyd-Steinberg algo dithers the image into a palette with fixed colours per channel
|
409 |
+
|
410 |
+
F = 1/16 | _ * 7 |
|
411 |
+
| 3 5 1 |
|
412 |
+
|
413 |
+
Stucki
|
414 |
+
| * 8 4 |
|
415 |
+
= 1/42 | 2 4 8 4 2 |
|
416 |
+
| 1 2 4 2 1 |
|
417 |
+
|
418 |
+
where * is the current pixel
|
419 |
+
|
420 |
+
'''
|
421 |
+
|
422 |
+
def fs_dither(img, height=448, width=600):
|
423 |
+
|
424 |
+
arr = np.array(img, dtype=int)
|
425 |
+
|
426 |
+
for ir in range(height-1): #new_height):
|
427 |
+
|
428 |
+
for ic in range(width-1): # new_width):
|
429 |
+
|
430 |
+
matches = colour_map - np.tile(arr[ir, ic],(NUMBER_COLOURS,1))
|
431 |
+
dist_match = np.apply_along_axis(np.linalg.norm,1,matches)
|
432 |
+
best_match = np.argmin(dist_match)
|
433 |
+
best_colour = colour_map[best_match]
|
434 |
+
diff_best = arr[ir,ic] - best_colour
|
435 |
+
dist_best = np.linalg.norm(diff_best)
|
436 |
+
|
437 |
+
# Clamp to best colour
|
438 |
+
|
439 |
+
arr[ir,ic] = best_colour
|
440 |
+
|
441 |
+
# Error spread to adjacent pixels
|
442 |
+
|
443 |
+
if dist_best > 88: # Error threshold to prevent unnecessary dithering - 88 = ~20% error 45 ~ 10% 66 - 15%
|
444 |
+
|
445 |
+
for factor,ir_d,ic_d in [(7,0,1),(3,1,-1),(5,1,0),(1,1,1)]:
|
446 |
+
|
447 |
+
arr[ir+ir_d, ic+ic_d][0] = min(arr[ir+ir_d, ic+ic_d][0] + round(diff_best[0] * factor/16,0),255)
|
448 |
+
arr[ir+ir_d, ic+ic_d][1] = min(arr[ir+ir_d, ic+ic_d][1] + round(diff_best[1] * factor/16,0),255)
|
449 |
+
arr[ir+ir_d, ic+ic_d][2] = min(arr[ir+ir_d, ic+ic_d][2] + round(diff_best[2] * factor/16,0),255)
|
450 |
+
|
451 |
+
|
452 |
+
return Image.fromarray(np.uint8(arr))
|
453 |
+
|
454 |
+
|
455 |
+
|
456 |
+
# Create a new white image with the specified dimensions.
|
457 |
+
# Load the JPEG image.
|
458 |
+
# Rescale the JPEG image to have a height of 448 pixels while maintaining its aspect ratio.
|
459 |
+
# Paste the rescaled image onto the white image, aligning it to the left.
|
460 |
+
|
461 |
+
# from PIL import Image
|
462 |
+
|
463 |
+
def load_image(input_image):
|
464 |
+
|
465 |
+
new_image = Image.new("RGB", (600, 448), "white") # Create a new white image with a width of 600 pixels and a height of 448 pixels
|
466 |
+
|
467 |
+
jpeg_image = Image.open(input_image) # Load the JPEG image
|
468 |
+
|
469 |
+
aspect_ratio = jpeg_image.width / jpeg_image.height # Rescale the JPEG image to have a height of 448 pixels and calculate the new width to maintain the aspect ratio
|
470 |
+
new_height = 448
|
471 |
+
new_width = int(aspect_ratio * new_height)
|
472 |
+
rescaled_image = jpeg_image.resize((new_width, new_height)) #, Image.ANTIALIAS)
|
473 |
+
|
474 |
+
new_image.paste(rescaled_image, (0, 0)) # Paste the rescaled image onto the white image, aligning it to the left
|
475 |
+
|
476 |
+
return new_image
|
477 |
+
|
478 |
+
# Save or display the result
|
479 |
+
# new_image.show() # To display the image
|
480 |
+
# white_image.save("output.jpg") # To save the image
|
481 |
+
|
482 |
+
|
483 |
+
|
484 |
+
# =================================
|
485 |
+
# MAIN PROGRAM
|
486 |
+
# =================================
|
487 |
+
|
488 |
+
'''
|
489 |
+
|
490 |
+
The display dimensions of the ePaper device is
|
491 |
+
|
492 |
+
width - 600
|
493 |
+
height - 448
|
494 |
+
|
495 |
+
We need to adjust according
|
496 |
+
|
497 |
+
TODO - rotate image based on best fit
|
498 |
+
|
499 |
+
'''
|
500 |
+
|
501 |
+
# rename files
|
502 |
+
|
503 |
+
##input_files = [file.name for file in os.scandir() if file.name[-3:]=="jpg"]
|
504 |
+
##
|
505 |
+
##for idx,input_file in enumerate(input_files):
|
506 |
+
## day_of_year = (idx % 365) + 1
|
507 |
+
## file_number = idx // 365
|
508 |
+
## new_file_name = "%03d%03d.jpg" % (day_of_year, file_number)
|
509 |
+
## print(new_file_name)
|
510 |
+
##
|
511 |
+
|
512 |
+
|
513 |
+
|
514 |
+
|
515 |
+
|
516 |
+
sys.exit()
|
517 |
+
|
518 |
+
|
519 |
+
|
520 |
+
|
521 |
+
|
522 |
+
start_time = dt.now()
|
523 |
+
print("Running",start_time)
|
524 |
+
|
525 |
+
input_files = [file.name for file in os.scandir() if file.name[-3:]=="jpg" and not os.path.exists(file.name[:-3]+"bmp")]
|
526 |
+
|
527 |
+
for input_image in input_files:
|
528 |
+
|
529 |
+
print("Processing:",input_image)
|
530 |
+
|
531 |
+
img = load_image(input_image)
|
532 |
+
dim = fs_dither(img)
|
533 |
+
|
534 |
+
if dim is not None:
|
535 |
+
dim.save(input_image[:-3]+"bmp")
|
536 |
+
else:
|
537 |
+
print("ERROR: No image returned for",input_image)
|
538 |
+
|
539 |
+
|
540 |
+
print("Finished",dt.now()-start_time)
|
541 |
+
|
542 |
+
|
543 |
+
|
544 |
+
|
545 |
+
|
546 |
+
|
547 |
+
|
548 |
+
|
549 |
+
|
550 |
+
|
551 |
+
|
552 |
+
|
553 |
+
|
554 |
+
'''
|
555 |
+
|
556 |
+
from PIL import Image
|
557 |
+
|
558 |
+
# Open the larger calendar image
|
559 |
+
calendar = Image.open("calendar.bmp")
|
560 |
+
|
561 |
+
# Open the smaller weather image
|
562 |
+
weather = Image.open("weather.bmp")
|
563 |
+
|
564 |
+
# Get the dimensions of the calendar image
|
565 |
+
calendar_width, calendar_height = calendar.size
|
566 |
+
|
567 |
+
# Get the dimensions of the weather image
|
568 |
+
weather_width, weather_height = weather.size
|
569 |
+
|
570 |
+
# Calculate the position to paste the weather image
|
571 |
+
position = (calendar_width - weather_width, calendar_height - weather_height)
|
572 |
+
|
573 |
+
# Paste the weather image onto the calendar image at the calculated position
|
574 |
+
calendar.paste(weather, position)
|
575 |
+
|
576 |
+
# Save the modified calendar image
|
577 |
+
calendar.save("calendar_with_weather.bmp")
|
578 |
+
|
579 |
+
# Optionally, show the result
|
580 |
+
calendar.show()
|
581 |
+
|
582 |
+
|
583 |
+
|
584 |
+
|
585 |
+
|
586 |
+
|
587 |
+
|
588 |
+
|
589 |
+
|
590 |
+
|
591 |
+
|
592 |
+
|
593 |
+
|
594 |
+
|
595 |
+
|
596 |
+
|
597 |
+
|
598 |
+
|
599 |
+
|
600 |
+
# test calendar code
|
601 |
+
|
602 |
+
from datetime import datetime
|
603 |
+
|
604 |
+
# Get the current date
|
605 |
+
current_date = datetime.now()
|
606 |
+
|
607 |
+
# Extract the current year and month as integers
|
608 |
+
current_year = current_date.year
|
609 |
+
current_month = current_date.month
|
610 |
+
|
611 |
+
# Print the year and month
|
612 |
+
print("Year:", current_year)
|
613 |
+
print("Month:", current_month)
|
614 |
+
|
615 |
+
current_date = datetime.now()
|
616 |
+
|
617 |
+
print(current_date.strftime("%A"))
|
618 |
+
|
619 |
+
print(current_date.strftime("%b %d, %Y"))
|
620 |
+
|
621 |
+
import calendar
|
622 |
+
|
623 |
+
yy = 2025
|
624 |
+
mm = 1
|
625 |
+
|
626 |
+
cal = calendar.month(yy, mm)
|
627 |
+
cal_lines = cal.split(chr(10))
|
628 |
+
|
629 |
+
for cal_line in cal_lines[1:]:
|
630 |
+
print(cal_line)
|
631 |
+
|
632 |
+
#print(cal)
|
633 |
+
#print(cal_lines)
|
634 |
+
|
635 |
+
#for char in cal:
|
636 |
+
# print(ord(char))
|
637 |
+
|
638 |
+
|
639 |
+
|
640 |
+
|
641 |
+
# Downloader
|
642 |
+
|
643 |
+
##pipe = cDataPipe()
|
644 |
+
##pipe.new_pipe(html_list_iter([r"https://creator.nightcafe.studio/"]))
|
645 |
+
##pipe.add_pipe(get_links,re.compile(r"(https:\/\/images\.nightcafe\.studio\/(?!\/assets)[A-z0-9\/\.\-]*jpg)"))
|
646 |
+
##pipe.add_pipe(pipe_inspector)
|
647 |
+
###pipe.end_pipe(cDownloader(file_namer="COUNTER",file_extension=".jpg").download)
|
648 |
+
##pipe.flow()
|
649 |
+
##
|
650 |
+
##sys.exit()
|
651 |
+
|
652 |
+
|
653 |
+
|
654 |
+
|
655 |
+
|
656 |
+
# width, height = img.size
|
657 |
+
#print(img.__dict__) #, width, height)
|
658 |
+
#new_width = 600
|
659 |
+
#new_height = int(height * new_width / width)
|
660 |
+
#img = img.resize((new_width, new_height), Image.ANTIALIAS)
|
661 |
+
|
662 |
+
def fs_dither_old(img, nc=2):
|
663 |
+
|
664 |
+
arr = np.array(img, dtype=float) / 255
|
665 |
+
|
666 |
+
for ir in range(new_height):
|
667 |
+
|
668 |
+
for ic in range(new_width):
|
669 |
+
|
670 |
+
# NB need to copy here for RGB arrays otherwise err will be (0,0,0)!
|
671 |
+
|
672 |
+
old_val = arr[ir, ic].copy()
|
673 |
+
new_val = get_new_val(old_val, nc)
|
674 |
+
arr[ir, ic] = new_val
|
675 |
+
err = old_val - new_val
|
676 |
+
|
677 |
+
# In this simple example, we will just ignore the border pixels.
|
678 |
+
|
679 |
+
if ic < new_width - 1:
|
680 |
+
arr[ir, ic+1] += (err * 7/16)
|
681 |
+
if ir < new_height - 1:
|
682 |
+
if ic > 0:
|
683 |
+
arr[ir+1, ic-1] += (err * 3/16)
|
684 |
+
arr[ir+1, ic] += (err * 5/16)
|
685 |
+
if ic < new_width - 1:
|
686 |
+
arr[ir+1, ic+1] += (err / 16)
|
687 |
+
|
688 |
+
carr = np.array(arr/np.max(arr, axis=(0,1)) * 255, dtype=np.uint8)
|
689 |
+
|
690 |
+
return Image.fromarray(carr)
|
691 |
+
|
692 |
+
|
693 |
+
|
694 |
+
'''
|
weather_icon.bmp
ADDED
![]() |