关于svg动画:Logo小变动心境大不同SVG矢量动画格式网站Logo图片制作与实践教程Python3
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_207 曾几何时,SVG(Scalable Vector Graphics)矢量动画图被坊间称之为一种被浏览器咒骂的技术,只因为蹩脚的硬件反对(IE),和没完没了的兼容性调优(Safari)。然而在2022年的明天,所有都不一样了,正所谓三十年河东,三十年河西,微软所研发的采纳Chromium内核作为IE替代者的Edge浏览器曾经无望超过Safari成为第二大桌面浏览器,而已经因为不反对关键帧动画被人诟病的Safari也对立了规范,市面上除了老帮菜IE,简直所有平台(包含挪动端)都曾经对SVG足够敌对,这让咱们能够放心大胆的在网站上利用SVG矢量动画图。 目前国内外相对来说技术后行的平台都曾经采纳SVG矢量格局,比方线上媒体界的巨擘Netflix、社交平台Twitter和国内的B站: 总的来说,采纳SVG格局图像的益处还是比拟多的,因为 SVG 图像是矢量图像,能够有限缩放,而且在图像品质降落方面没有任何问题。为什么会这样呢?因为 SVG 图像是应用 XML 标记构建的,浏览器通过绘制每个点和线来打印它们。这确保 SVG 图像能够适应不同的屏幕大小和分辨率,传统图像格式(如 PNG、GIF 或 JPG)是通过预约义的像素来填充色调空间,这就导致屏幕素质会对图像的成像品质产生影响。 举个例子,传统像素图片就像是食堂里曾经烙好的饼,是多大就是多大,碰上胃口好的,饼不够大,就会产生影响,而SVG是通过XML技术把饼的轮廓和技术参数定义好,由浏览器实时绘制,能够依据胃口的大小自适应饼的大小,达到矢量的目标。同时,因为是在 XML 中定义的,SVG 图像比 JPG 或 PNG 图像更灵便,而且咱们能够应用 CSS 和 JavaScript 与它们进行交互。另外从文件体积角度上讲,SVG并不比PNG更大,反而压缩之后体积更小,最初作为XML的网页格局,间接在浏览器中解析,所以不须要独自的带宽进行申请,节约了网络申请数,百利而无一害。 接下来的传统节目应该是介绍SVG根本语法,而后画几个不痛不痒的简略矢量图,展示一下SVG个性,最初草草了事,那样就太无趣了,本次咱们来点“快餐”,简略粗犷的将PNG的Logo图像间接转化成SVG格局,略过“绘制”的步骤,五分钟内间接让你的网站Logo“芜湖腾飞”。 SVG图像转化与压缩以本站的Logo为例子,首先须要一个PNG矢量图: 留神图像色彩位数最好越小越好,这样转化后的图像不会过大,同时背景最好是通明的,因为通明元素不会被XML文件进行标记,达到节约空间的目标,这里咱们以PNG仿色4位的位图为例子。 转化形式有很多种,能够通过Python3的三方图像库: pip3 install Pillow编写转化脚本test.py: import sys import os import operator from collections import deque import io from optparse import OptionParser from PIL import Image def add_tuple(a, b): return tuple(map(operator.add, a, b)) def sub_tuple(a, b): return tuple(map(operator.sub, a, b)) def neg_tuple(a): return tuple(map(operator.neg, a)) def direction(edge): return sub_tuple(edge[1], edge[0]) def magnitude(a): return int(pow(pow(a[0], 2) + pow(a[1], 2), .5)) def normalize(a): mag = magnitude(a) assert mag > 0, "Cannot normalize a zero-length vector" return tuple(map(operator.truediv, a, [mag] * len(a))) def svg_header(width, height): return """<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg" version="1.1"> """ % (width, height) def rgba_image_to_svg_pixels(im): s = io.StringIO() s.write(svg_header(*im.size)) width, height = im.size for x in range(width): for y in range(height): here = (x, y) rgba = im.getpixel(here) if not rgba[3]: continue s.write( """ <rect x="%d" y="%d" width="1" height="1" style="fill:rgb%s; fill-opacity:%.3f; stroke:none;" />\n""" % (x, y, rgba[0:3], float(rgba[3]) / 255)) print("Converting pixels: " + str(x * 100 / width) + "%") s.write("""</svg>\n""") return s.getvalue() def joined_edges(assorted_edges, keep_every_point=False): pieces = [] piece = [] directions = deque([ (0, 1), (1, 0), (0, -1), (-1, 0), ]) while assorted_edges: if not piece: piece.append(assorted_edges.pop()) current_direction = normalize(direction(piece[-1])) while current_direction != directions[2]: directions.rotate() for i in range(1, 4): next_end = add_tuple(piece[-1][1], directions[i]) next_edge = (piece[-1][1], next_end) if next_edge in assorted_edges: assorted_edges.remove(next_edge) if i == 2 and not keep_every_point: # same direction piece[-1] = (piece[-1][0], next_edge[1]) else: piece.append(next_edge) if piece[0][0] == piece[-1][1]: if not keep_every_point and normalize(direction( piece[0])) == normalize(direction(piece[-1])): piece[-1] = (piece[-1][0], piece.pop(0)[1]) # same direction pieces.append(piece) piece = [] break else: raise Exception("Failed to find connecting edge") return pieces def rgba_image_to_svg_contiguous(im, keep_every_point=False): # collect contiguous pixel groups adjacent = ((1, 0), (0, 1), (-1, 0), (0, -1)) visited = Image.new("1", im.size, 0) color_pixel_lists = {} width, height = im.size for x in range(width): for y in range(height): here = (x, y) if visited.getpixel(here): continue rgba = im.getpixel((x, y)) if not rgba[3]: continue piece = [] queue = [here] visited.putpixel(here, 1) while queue: here = queue.pop() for offset in adjacent: neighbour = add_tuple(here, offset) if not (0 <= neighbour[0] < width) or not (0 <= neighbour[1] < height): continue if visited.getpixel(neighbour): continue neighbour_rgba = im.getpixel(neighbour) if neighbour_rgba != rgba: continue queue.append(neighbour) visited.putpixel(neighbour, 1) piece.append(here) if not rgba in color_pixel_lists: color_pixel_lists[rgba] = [] color_pixel_lists[rgba].append(piece) print("Converting image: " + str(round(x * 100 / width, 2)) + "%") del adjacent del visited # calculate clockwise edges of pixel groups edges = { (-1, 0): ((0, 0), (0, 1)), (0, 1): ((0, 1), (1, 1)), (1, 0): ((1, 1), (1, 0)), (0, -1): ((1, 0), (0, 0)), } color_edge_lists = {} counter = 0 for rgba, pieces in color_pixel_lists.items(): for piece_pixel_list in pieces: edge_set = set([]) for coord in piece_pixel_list: for offset, (start_offset, end_offset) in edges.items(): neighbour = add_tuple(coord, offset) start = add_tuple(coord, start_offset) end = add_tuple(coord, end_offset) edge = (start, end) if neighbour in piece_pixel_list: continue edge_set.add(edge) if not rgba in color_edge_lists: color_edge_lists[rgba] = [] color_edge_lists[rgba].append(edge_set) counter = counter + 1 print("Calculating edges: " + str(round(counter * 100 / len(color_pixel_lists.items()), 2)) + "%") del color_pixel_lists del edges # join edges of pixel groups color_joined_pieces = {} for color, pieces in color_edge_lists.items(): color_joined_pieces[color] = [] for assorted_edges in pieces: color_joined_pieces[color].append( joined_edges(assorted_edges, keep_every_point)) s = io.StringIO() s.write(svg_header(*im.size)) counter = 0 for color, shapes in color_joined_pieces.items(): for shape in shapes: s.write(""" <path d=" """) for sub_shape in shape: here = sub_shape.pop(0)[0] s.write(""" M %d,%d """ % here) for edge in sub_shape: here = edge[0] s.write(""" L %d,%d """ % here) s.write(""" Z """) s.write( """ " style="fill:rgb%s; fill-opacity:%.3f; stroke:none;" />\n""" % (color[0:3], float(color[3]) / 255)) counter = counter + 1 print("Joining edges: " + str(round(counter * 100 / len(color_joined_pieces.items()), 2)) + "%") s.write("""</svg>\n""") return s.getvalue() def png_to_svg(filename, contiguous=None, keep_every_point=None): try: im = Image.open(filename) except IOError as e: sys.stderr.write('%s: Could not open as image file\n' % filename) sys.exit(1) im_rgba = im.convert('RGBA') if contiguous: return rgba_image_to_svg_contiguous(im_rgba, keep_every_point) else: return rgba_image_to_svg_pixels(im_rgba) if __name__ == "__main__": parser = OptionParser() parser.add_option( "-p", "--pixels", action="store_false", dest="contiguous", help= "Generate a separate shape for each pixel; do not group pixels into contiguous areas of the same colour", default=True) parser.add_option( "-1", "--one", action="store_true", dest="keep_every_point", help= "1-pixel-width edges on contiguous shapes; default is to remove intermediate points on straight line edges. ", default=None) (options, args) = parser.parse_args() if (len(sys.argv)) < 2: for file in os.listdir("."): if file.endswith(".png"): print("Converting " + file) f = open(file.replace(".png", ".svg"), 'w') f.write( png_to_svg(file, contiguous=options.contiguous, keep_every_point=options.keep_every_point)) else: for file in sys.argv: if file.endswith(".png"): print("Converting " + file) f = open(file.replace(".png", ".svg"), 'w') f.write( png_to_svg(file, contiguous=options.contiguous, keep_every_point=options.keep_every_point))运行脚本文件: ...