Initial text support implementation, pt. II.
Switch from fixed-function pipeline to shaders.
10
ENNTool.py
@ -10,7 +10,7 @@
|
||||
# 3) -s: effects: rotate, smash into bricks, swirl, wave, ...
|
||||
# 4) XXX autodetect video width from widest mircart
|
||||
# 5) XXX convert TTF to texture PNG & coords TXT, render accordingly
|
||||
# 6) XXX dont stall GPU w/ glReadPixels(), switch to asynchronous model w/ PBOs
|
||||
# 6) XXX dont stall GPU w/ glReadPixels(), switch to asynchronous model w/ FBO or PBO (http://www.songho.ca/opengl/gl_fbo.html, http://www.songho.ca/opengl/gl_pbo.html)
|
||||
# 7) XXX render mircart as 3D blocks vs flat surface
|
||||
#
|
||||
|
||||
@ -91,8 +91,8 @@ class ENNToolApp(object):
|
||||
MiRCART += ENNToolMiRCARTImporter(inFile).outMap
|
||||
|
||||
curY, rotateX, rotateY, translateY = 0, 0, 0, scrollRate
|
||||
artTextureId = panelGLCanvas.initTexture(texturePathName)
|
||||
artVbo, artVboLen, lastY, numVertices = panelGLCanvas.renderMiRCART(MiRCART, cubeSize=optdict["-R"])
|
||||
artTextureId, artInfo = panelGLCanvas.initTexture(texturePathName)
|
||||
artVbo, artVboLen, lastY, numVertices = panelGLCanvas.renderMiRCART(artInfo, MiRCART, cubeSize=optdict["-R"])
|
||||
if "-v" in optdict:
|
||||
print("{} vertices".format(numVertices))
|
||||
w, h = panelGLCanvas.GetClientSize(); w, h = max(w, 1.0), max(h, 1.0);
|
||||
@ -121,7 +121,9 @@ class ENNToolApp(object):
|
||||
|
||||
videoFps, videoPath = int(optdict["-f"]), optdict["-o"]
|
||||
panelGLCanvas = ENNToolGLCanvasPanel(appPanelSkin, size=appFrameSize, videoPath=videoPath)
|
||||
panelGLCanvas.initOpenGL(); panelGLCanvas.initVideoWriter(fps=videoFps)
|
||||
panelGLCanvas.initOpenGL()
|
||||
panelGLCanvas.initShaders()
|
||||
panelGLCanvas.initVideoWriter(fps=videoFps)
|
||||
|
||||
if "-v" in optdict:
|
||||
time0 = time.time()
|
||||
|
@ -10,13 +10,20 @@
|
||||
# Wed, 27 Jun 2018 16:02:12 +0200 [3] <https://www.khronos.org/opengl/wiki/How_lighting_works#Good_Settings.>
|
||||
# Wed, 27 Jun 2018 16:02:13 +0200 [4] <https://www.khronos.org/opengl/wiki/Common_Mistakes>
|
||||
# Wed, 27 Jun 2018 16:02:14 +0200 [5] <https://www.khronos.org/opengl/wiki/Pixel_Transfer#Pixel_layout>
|
||||
# Thu, 28 Jun 2018 17:03:16 +0200 [6] <https://stackoverflow.com/questions/384759/how-to-convert-a-pil-image-into-a-numpy-array>
|
||||
# Thu, 28 Jun 2018 17:04:59 +0200 [7] <https://www.khronos.org/opengl/wiki/Common_Mistakes#y-axis>
|
||||
# Thu, 28 Jun 2018 18:32:50 +0200 [8] <https://stackoverflow.com/questions/18935203/shader-position-vec4-or-vec3>
|
||||
#
|
||||
|
||||
from OpenGL.GL import *
|
||||
from OpenGL.GL import shaders
|
||||
from PIL import Image
|
||||
import cv2, numpy
|
||||
import ctypes, sys
|
||||
import ctypes, os, sys, time
|
||||
import wx, wx.glcanvas
|
||||
import yaml
|
||||
|
||||
from ENNToolMiRCARTColours import ENNToolMiRCARTColoursFloat
|
||||
|
||||
class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
|
||||
"""XXX"""
|
||||
@ -34,22 +41,56 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
glClearColor(0, 0, 0, 1); glClearDepth(1);
|
||||
glTranslatef(-5.0, 3.0, -5)
|
||||
|
||||
# [3]
|
||||
glLight(GL_LIGHT0, GL_AMBIENT, (0, 0, 0, 1))
|
||||
glLight(GL_LIGHT0, GL_DIFFUSE, (1, 1, 1, 1))
|
||||
glLight(GL_LIGHT0, GL_POSITION, (1, 1, 0+2, 0))
|
||||
glLight(GL_LIGHT0, GL_SPECULAR, (1, 1, 1, 1))
|
||||
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (0.2, 0.2, 0.2, 1))
|
||||
glEnable(GL_LIGHTING); glEnable(GL_LIGHT0);
|
||||
glEnable(GL_COLOR_MATERIAL)
|
||||
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
|
||||
# }}}
|
||||
# {{{ initTexture(self, pathName): XXX
|
||||
def initTexture(self, pathName):
|
||||
# {{{ initShaders(self): XXX
|
||||
def initShaders(self):
|
||||
fs = shaders.compileShader("""
|
||||
#version 330 core
|
||||
|
||||
in vec4 bgColour;
|
||||
in vec2 fgTexCoord;
|
||||
uniform sampler2D texture;
|
||||
|
||||
void main() {
|
||||
vec4 texel = texture2D(texture, fgTexCoord);
|
||||
if (texel.a > 0.0) {
|
||||
gl_FragColor = vec4(texel.r, texel.g, texel.b, 1.0);
|
||||
} else {
|
||||
gl_FragColor = bgColour;
|
||||
}
|
||||
}
|
||||
""", GL_FRAGMENT_SHADER)
|
||||
vs = shaders.compileShader("""
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec4 vertex;
|
||||
layout(location = 1) in vec3 normal;
|
||||
layout(location = 2) in vec4 colour;
|
||||
layout(location = 3) in vec2 texcoord;
|
||||
|
||||
out vec4 bgColour;
|
||||
out vec2 fgTexCoord;
|
||||
|
||||
uniform mat4 model;
|
||||
uniform mat4 projection;
|
||||
|
||||
void main() {
|
||||
gl_Position = projection * model * vertex;
|
||||
bgColour = colour;
|
||||
fgTexCoord = texcoord;
|
||||
}
|
||||
""", GL_VERTEX_SHADER)
|
||||
self.shader = shaders.compileProgram(vs, fs)
|
||||
# }}}
|
||||
# {{{ initTexture(self, pathName, infoPathName=os.path.join("assets", "textures.yaml")): XXX
|
||||
def initTexture(self, pathName, infoPathName=os.path.join("assets", "textures.yaml")):
|
||||
with open(infoPathName, "r") as fileObject:
|
||||
artInfo = yaml.load(fileObject)
|
||||
|
||||
# [6], [7]
|
||||
artTextureId = glGenTextures(1)
|
||||
artTextureImage = Image.open(pathName)
|
||||
artTextureImageData = numpy.array(list(artTextureImage.getdata()), numpy.uint8)
|
||||
artTextureImage = Image.open(pathName).transpose(Image.FLIP_TOP_BOTTOM)
|
||||
artTextureImageData = numpy.array(artTextureImage)
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, artTextureId)
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
|
||||
@ -61,11 +102,11 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
||||
|
||||
# [4][5]
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
|
||||
artTextureImage.size[0], artTextureImage.size[1],
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, artTextureImageData)
|
||||
glBindTexture(GL_TEXTURE_2D, artTextureId)
|
||||
return artTextureId
|
||||
return artTextureId, artInfo
|
||||
# }}}
|
||||
# {{{ initVideoWriter(self): XXX
|
||||
def initVideoWriter(self, fourcc="XVID", fps=25):
|
||||
@ -76,54 +117,94 @@ class ENNToolGLCanvasPanel(wx.glcanvas.GLCanvas, wx.Panel):
|
||||
def renderFrame(self, artTextureId, artVbo, artVboLen):
|
||||
glEnableClientState(GL_VERTEX_ARRAY)
|
||||
glEnableClientState(GL_NORMAL_ARRAY)
|
||||
glEnableClientState(GL_COLOR_ARRAY)
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
|
||||
glEnable(GL_BLEND)
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, artTextureId)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, artVbo)
|
||||
glVertexPointer(3, GL_FLOAT, 32, ctypes.c_void_p(0))
|
||||
glNormalPointer(GL_FLOAT, 32, ctypes.c_void_p(12))
|
||||
glTexCoordPointer(2, GL_FLOAT, 32, ctypes.c_void_p(24))
|
||||
|
||||
glUseProgram(self.shader)
|
||||
model = (GLfloat * 16)()
|
||||
glGetFloatv(GL_MODELVIEW_MATRIX, model)
|
||||
projection = (GLfloat * 16)()
|
||||
glGetFloatv(GL_PROJECTION_MATRIX, projection)
|
||||
glUniformMatrix4fv(glGetUniformLocation(self.shader, "model"), 1, GL_FALSE, model)
|
||||
glUniformMatrix4fv(glGetUniformLocation(self.shader, "projection"), 1, GL_FALSE, projection)
|
||||
glUniform1i(glGetUniformLocation(self.shader, "texture"), 0)
|
||||
|
||||
# [8]
|
||||
glEnableVertexAttribArray(0)
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, False, 48, ctypes.c_void_p(0))
|
||||
glEnableVertexAttribArray(1)
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, False, 48, ctypes.c_void_p(12))
|
||||
glEnableVertexAttribArray(2)
|
||||
glVertexAttribPointer(2, 4, GL_FLOAT, False, 48, ctypes.c_void_p(24))
|
||||
glEnableVertexAttribArray(3)
|
||||
glVertexAttribPointer(3, 2, GL_FLOAT, False, 48, ctypes.c_void_p(40))
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
|
||||
glVertexPointer(3, GL_FLOAT, 48, ctypes.c_void_p(0))
|
||||
glNormalPointer(GL_FLOAT, 48, ctypes.c_void_p(12))
|
||||
glColorPointer(4, GL_FLOAT, 48, ctypes.c_void_p(24))
|
||||
glTexCoordPointer(2, GL_FLOAT, 48, ctypes.c_void_p(40))
|
||||
glDrawArrays(GL_QUADS, 0, artVboLen)
|
||||
|
||||
glDisableVertexAttribArray(0)
|
||||
glDisable(GL_BLEND)
|
||||
glDisable(GL_TEXTURE_2D)
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
|
||||
glDisableClientState(GL_COLOR_ARRAY)
|
||||
glDisableClientState(GL_NORMAL_ARRAY)
|
||||
glDisableClientState(GL_VERTEX_ARRAY)
|
||||
# }}}
|
||||
# {{{ renderMiRCART(self, artMap, centre=True, canvasCols=100, cubeSize=(0.1, 0.2), texelHeight=0.0625, texelWidth=0.0625): XXX
|
||||
def renderMiRCART(self, artMap, centre=True, canvasCols=100, cubeSize=(0.1, 0.2), texelHeight=0.0625, texelWidth=0.0625):
|
||||
# {{{ renderMiRCART(self, artInfo, artMap, centre=True, canvasCols=100, cubeSize=(0.1, 0.2)): XXX
|
||||
def renderMiRCART(self, artInfo, artMap, centre=True, canvasCols=100, cubeSize=(0.1, 0.2)):
|
||||
curPos = [0, 0, 0]; vertices = []; numVertices = 0;
|
||||
for numRow in range(len(artMap)):
|
||||
if centre and (len(artMap[numRow]) < canvasCols):
|
||||
curPos[0] += (((canvasCols - len(artMap[numRow])) * cubeSize[0]) / 2)
|
||||
for numCol in range(len(artMap[numRow])):
|
||||
cubeColour = artMap[numRow][numCol][1] * texelWidth
|
||||
cubeColour = [*ENNToolMiRCARTColoursFloat[artMap[numRow][numCol][1]], 1.0]
|
||||
if artMap[numRow][numCol][0] != artMap[numRow][numCol][1]:
|
||||
colColour = artMap[numRow][numCol][0]
|
||||
cubeChar = artMap[numRow][numCol][3]
|
||||
if ord(cubeChar) >= 128:
|
||||
print("dont have {}".format(cubeChar))
|
||||
cubeChar = " "
|
||||
else:
|
||||
colColour = artMap[numRow][numCol][1]
|
||||
cubeChar = " "
|
||||
|
||||
# Top Right
|
||||
vertices += curPos
|
||||
vertices += [0.0, 0.0, 1.0]
|
||||
vertices += [cubeColour+texelWidth, texelHeight]
|
||||
vertices += cubeColour
|
||||
vertices += [float(((ord(cubeChar) + 1) * artInfo["rowWidth"]) / artInfo["texWidth"]), ((colColour + 1) * artInfo["rowHeight"]) / float(artInfo["texHeight"])]
|
||||
numVertices += 1
|
||||
|
||||
# Top Left
|
||||
vertices += [curPos[0]-cubeSize[0], curPos[1], curPos[2]]
|
||||
vertices += [0.0, 0.0, 1.0]
|
||||
vertices += [cubeColour, texelHeight]
|
||||
vertices += cubeColour
|
||||
vertices += [float(((ord(cubeChar) + 0) * artInfo["rowWidth"]) / artInfo["texWidth"]), ((colColour + 1) * artInfo["rowHeight"]) / float(artInfo["texHeight"])]
|
||||
numVertices += 1
|
||||
|
||||
# Bottom Left
|
||||
vertices += [curPos[0]-cubeSize[0], curPos[1]-cubeSize[1], curPos[2]]
|
||||
vertices += [0.0, 0.0, 1.0]
|
||||
vertices += [cubeColour, 0.0]
|
||||
vertices += cubeColour
|
||||
vertices += [float(((ord(cubeChar) + 0) * artInfo["rowWidth"]) / artInfo["texWidth"]), ((colColour) * artInfo["rowHeight"]) / float(artInfo["texHeight"])]
|
||||
numVertices += 1
|
||||
|
||||
# Bottom Right
|
||||
vertices += [curPos[0], curPos[1]-cubeSize[1], curPos[2]]
|
||||
vertices += [0.0, 0.0, 1.0]
|
||||
vertices += [cubeColour+texelWidth, 0.0]
|
||||
vertices += cubeColour
|
||||
vertices += [float(((ord(cubeChar) + 1) * artInfo["rowWidth"]) / artInfo["texWidth"]), ((colColour) * artInfo["rowHeight"]) / float(artInfo["texHeight"])]
|
||||
numVertices += 1
|
||||
|
||||
curPos[0] += cubeSize[0]
|
||||
|
@ -16,75 +16,61 @@ from ENNToolMiRCARTImporter import ENNToolMiRCARTImporter
|
||||
def main(*argv):
|
||||
fontNormalPathName = os.path.join("assets", "DejaVuSansMono.ttf")
|
||||
fontBoldPathName = os.path.join("assets", "DejaVuSansMono-Bold.ttf")
|
||||
fontSize = int("11")
|
||||
fontSize = int("26")
|
||||
outInfoFileName = os.path.join("assets", "textures.yaml")
|
||||
outPathName = os.path.join("assets", "textures")
|
||||
outFileName = os.path.join("assets", "DejaVuSansMono.png")
|
||||
if not os.path.exists(os.path.dirname(outInfoFileName)):
|
||||
os.makedirs(os.path.dirname(outInfoFileName))
|
||||
if not os.path.exists(outPathName):
|
||||
os.makedirs(outPathName)
|
||||
if not os.path.exists(os.path.dirname(outFileName)):
|
||||
os.makedirs(os.path.dirname(outFileName))
|
||||
|
||||
pilFontNormal = ImageFont.truetype(fontNormalPathName, fontSize)
|
||||
pilFontBold = ImageFont.truetype(fontBoldPathName, fontSize)
|
||||
pilFontSize = list(pilFontNormal.getsize(" "))
|
||||
pilFontSize[0] += (8 - (pilFontSize[0] % 8))
|
||||
pilFontSize[1] = pilFontSize[0] * 2
|
||||
pilImageSize = (pilFontSize[0] * 128, pilFontSize[1])
|
||||
pilImageSize = (pilFontSize[0] * 128, (pilFontSize[1] * 16 * 4))
|
||||
print("font size: {}, image size: {}".format(pilFontSize, pilImageSize))
|
||||
|
||||
charMap = {}
|
||||
for fontAttrs in [[ENNToolMiRCARTImporter._CellState.CS_NONE],
|
||||
[ENNToolMiRCARTImporter._CellState.CS_BOLD],
|
||||
[ENNToolMiRCARTImporter._CellState.CS_UNDERLINE],
|
||||
[ENNToolMiRCARTImporter._CellState.CS_BOLD, ENNToolMiRCARTImporter._CellState.CS_UNDERLINE]]:
|
||||
for fontColour in range(16):
|
||||
curPos = [0, 0]
|
||||
pilImage = Image.new("RGBA", pilImageSize, (0, 0, 0, 0))
|
||||
pilImageDraw = ImageDraw.Draw(pilImage)
|
||||
curPos = [0, 0]
|
||||
pilImage = Image.new("RGBA", pilImageSize, (0, 0, 0, 0))
|
||||
pilImageDraw = ImageDraw.Draw(pilImage)
|
||||
|
||||
pilImageTmp = Image.new("RGBA", pilFontSize, (0, 0, 0, 0))
|
||||
pilImageTmpDraw = ImageDraw.Draw(pilImageTmp)
|
||||
|
||||
for fontAttr in [ENNToolMiRCARTImporter._CellState.CS_BOLD | ENNToolMiRCARTImporter._CellState.CS_UNDERLINE,
|
||||
ENNToolMiRCARTImporter._CellState.CS_UNDERLINE,
|
||||
ENNToolMiRCARTImporter._CellState.CS_BOLD,
|
||||
ENNToolMiRCARTImporter._CellState.CS_NONE]:
|
||||
for fontColour in reversed(range(16)):
|
||||
for fontChar in [chr(n) for n in range(128)]:
|
||||
pilFont, underLine = None, False
|
||||
for fontAttr in fontAttrs:
|
||||
if fontAttr == ENNToolMiRCARTImporter._CellState.CS_NONE:
|
||||
pilFont = pilFontNormal
|
||||
elif fontAttr == ENNToolMiRCARTImporter._CellState.CS_BOLD:
|
||||
pilFont = pilFontBold
|
||||
elif fontAttr == ENNToolMiRCARTImporter._CellState.CS_UNDERLINE:
|
||||
underLine = True
|
||||
else:
|
||||
raise ValueError
|
||||
pilFont, underLine = pilFontNormal, False
|
||||
if fontAttr & ENNToolMiRCARTImporter._CellState.CS_BOLD:
|
||||
pilFont = pilFontBold
|
||||
if fontAttr & ENNToolMiRCARTImporter._CellState.CS_UNDERLINE:
|
||||
underLine = True
|
||||
if fontChar in string.printable:
|
||||
pilImageDraw.text(curPos, fontChar,
|
||||
pilImageTmpDraw.text((0, 0), fontChar,
|
||||
(*ENNToolMiRCARTColours[fontColour], 255), pilFont)
|
||||
pilImage.paste(pilImageTmp, tuple(curPos))
|
||||
pilImageTmpDraw.rectangle((0, 0, pilFontSize[0], pilFontSize[1]),
|
||||
fill=(0, 0, 0, 0))
|
||||
if underLine:
|
||||
pilImageDraw.line(
|
||||
xy=(curPos[0], curPos[1] + (pilFontSize[1] - 2),
|
||||
curPos[0] + pilFontSize[0], curPos[1] + (pilFontSize[1] - 2)),
|
||||
fill=(*ENNToolMiRCARTColours[fontColour], 255))
|
||||
if not fontChar in charMap:
|
||||
charMap[fontChar] = {}
|
||||
if not fontColour in charMap[fontChar]:
|
||||
charMap[fontChar][fontColour] = []
|
||||
charMap[fontChar][fontColour] \
|
||||
+= [{"attrs":fontAttrs,
|
||||
"bl":[float((curPos[0])/pilImageSize[0]), float((curPos[1])/pilImageSize[1])],
|
||||
"br":[float((curPos[0] + pilFontSize[0])/pilImageSize[0]), float((curPos[1])/pilImageSize[1])],
|
||||
"tl":[float((curPos[0])/pilImageSize[0]), float((curPos[1] + pilFontSize[1])/pilImageSize[1])],
|
||||
"tr":[float((curPos[0] + pilFontSize[0])/pilImageSize[0]), float((curPos[1] + pilFontSize[1])/pilImageSize[1])]}]
|
||||
curPos[0] += pilFontSize[0]
|
||||
fontAttrName = ""
|
||||
for fontAttr in fontAttrs:
|
||||
if fontAttr == ENNToolMiRCARTImporter._CellState.CS_NONE:
|
||||
fontAttrName += "Normal"
|
||||
elif fontAttr == ENNToolMiRCARTImporter._CellState.CS_BOLD:
|
||||
fontAttrName += "Bold"
|
||||
elif fontAttr == ENNToolMiRCARTImporter._CellState.CS_UNDERLINE:
|
||||
fontAttrName += "Underline"
|
||||
else:
|
||||
raise ValueError
|
||||
pilImage.save(os.path.join(outPathName, "{}Fg{:02d}.png".format(fontAttrName, fontColour)))
|
||||
|
||||
curPos[0], curPos[1] = 0, curPos[1] + pilFontSize[1]
|
||||
pilImage.save(outFileName)
|
||||
artInfo = {}
|
||||
artInfo["rowHeight"] = pilFontSize[1]
|
||||
artInfo["rowWidth"] = pilFontSize[0]
|
||||
artInfo["texHeight"] = pilImageSize[1]
|
||||
artInfo["texWidth"] = pilImageSize[0]
|
||||
with open(outInfoFileName, "w") as fileObject:
|
||||
yaml.dump(charMap, fileObject)
|
||||
yaml.dump(artInfo, fileObject)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(*sys.argv)
|
||||
|
BIN
assets/DejaVuSansMono.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 18 B |
1
assets/texture.png
Symbolic link
@ -0,0 +1 @@
|
||||
DejaVuSansMono.png
|
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 18 B |
33703
assets/textures.yaml
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |