365 lines
14 KiB
Python
365 lines
14 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Created on 2018年10月25日
|
|
@author: Irony
|
|
@site: https://pyqt.site , https://github.com/PyQt5
|
|
@email: 892768447@qq.com
|
|
@file: NinePatch
|
|
@description:
|
|
"""
|
|
from math import fabs
|
|
|
|
try:
|
|
from PyQt5.QtCore import QRect
|
|
from PyQt5.QtGui import QImage, QColor, QPainter, qRed, qGreen, qBlue, qAlpha
|
|
except ImportError:
|
|
from PySide2.QtCore import QRect
|
|
from PySide2.QtGui import QImage, QColor, QPainter, qRed, qGreen, qBlue, qAlpha
|
|
|
|
|
|
class _Exception(Exception):
|
|
|
|
def __init__(self, imgW, imgH):
|
|
self.imgW = imgW
|
|
self.imgH = imgH
|
|
|
|
|
|
class NinePatchException(Exception):
|
|
|
|
def __str__(self):
|
|
return "Nine patch error"
|
|
|
|
|
|
class ExceptionIncorrectWidth(_Exception):
|
|
|
|
def __str__(self):
|
|
return "Input incorrect width. Mimimum width = :{imgW}".format(imgW=self.imgW)
|
|
|
|
|
|
class ExceptionIncorrectWidthAndHeight(_Exception):
|
|
|
|
def __str__(self):
|
|
return "Input incorrect width width and height. Minimum width = :{imgW} . Minimum height = :{imgH}".format(
|
|
imgW=self.imgW, imgH=self.imgH)
|
|
|
|
|
|
class ExceptionIncorrectHeight(_Exception):
|
|
|
|
def __str__(self):
|
|
return "Input incorrect height. Minimum height = :{imgW}".format(imgW=self.imgW)
|
|
|
|
|
|
class ExceptionNot9Patch(Exception):
|
|
|
|
def __str__(self):
|
|
return "It is not nine patch image"
|
|
|
|
|
|
class NinePatch:
|
|
|
|
def __init__(self, fileName):
|
|
self.CachedImage = None # 缓存图片
|
|
self.OldWidth = -1
|
|
self.OldHeight = -1
|
|
self.ResizeDistancesX = []
|
|
self.ResizeDistancesY = [] # [(int,int)]数组
|
|
self.setImage(fileName)
|
|
|
|
def width(self):
|
|
return self.Image.width()
|
|
|
|
def height(self):
|
|
return self.Image.height()
|
|
|
|
def setImage(self, fileName):
|
|
self.Image = QImage(fileName)
|
|
if self.Image.isNull():
|
|
return
|
|
|
|
self.ContentArea = self.GetContentArea()
|
|
self.GetResizeArea()
|
|
if not self.ResizeDistancesX or not self.ResizeDistancesY:
|
|
raise ExceptionNot9Patch
|
|
|
|
def __del__(self):
|
|
if hasattr(self, "CachedImage"):
|
|
del self.CachedImage
|
|
if hasattr(self, "Image"):
|
|
del self.Image
|
|
|
|
def Draw(self, painter, x, y):
|
|
painter.drawImage(x, y, self.CachedImage)
|
|
|
|
def SetImageSize(self, width, height):
|
|
resizeWidth = 0
|
|
resizeHeight = 0
|
|
for i in range(len(self.ResizeDistancesX)):
|
|
resizeWidth += self.ResizeDistancesX[i][1]
|
|
|
|
for i in range(len(self.ResizeDistancesY)):
|
|
resizeHeight += self.ResizeDistancesY[i][1]
|
|
|
|
if (width < (self.Image.width() - 2 - resizeWidth) and height < (
|
|
self.Image.height() - 2 - resizeHeight)):
|
|
raise ExceptionIncorrectWidthAndHeight(
|
|
self.Image.width() - 2, self.Image.height() - 2)
|
|
|
|
if (width < (self.Image.width() - 2 - resizeWidth)):
|
|
raise ExceptionIncorrectWidth(
|
|
self.Image.width() - 2, self.Image.height() - 2)
|
|
|
|
if (height < (self.Image.height() - 2 - resizeHeight)):
|
|
raise ExceptionIncorrectHeight(
|
|
self.Image.width() - 2, self.Image.height() - 2)
|
|
|
|
if (width != self.OldWidth or height != self.OldHeight):
|
|
self.OldWidth = width
|
|
self.OldHeight = height
|
|
self.UpdateCachedImage(width, height)
|
|
|
|
@classmethod
|
|
def GetContentAreaRect(self, width, height):
|
|
# print("GetContentAreaRect : width:%d height:%d" % (width, height))
|
|
return (QRect(self.ContentArea.x(), self.ContentArea.y(),
|
|
(width - (self.Image.width() - 2 - self.ContentArea.width())),
|
|
(height - (self.Image.height() - 2 - self.ContentArea.height()))))
|
|
|
|
def DrawScaledPart(self, oldRect, newRect, painter):
|
|
if (newRect.width() and newRect.height()):
|
|
# print("DrawScaledPart newRect.width:%d newRect.height:%d" % (newRect.width() , newRect.height()))
|
|
img = self.Image.copy(oldRect)
|
|
img = img.scaled(newRect.width(), newRect.height())
|
|
painter.drawImage(newRect.x(), newRect.y(), img,
|
|
0, 0, newRect.width(), newRect.height())
|
|
|
|
def DrawConstPart(self, oldRect, newRect, painter):
|
|
# print("DrawConstPart oldRect:{oldRect} newRect:{newRect}".format(oldRect = oldRect, newRect = newRect))
|
|
img = self.Image.copy(oldRect)
|
|
painter.drawImage(newRect.x(), newRect.y(), img, 0,
|
|
0, newRect.width(), newRect.height())
|
|
|
|
def IsColorBlack(self, color):
|
|
r = qRed(color)
|
|
g = qGreen(color)
|
|
b = qBlue(color)
|
|
a = qAlpha(color)
|
|
if a < 128:
|
|
return False
|
|
return r < 128 and g < 128 and b < 128
|
|
|
|
def GetContentArea(self):
|
|
j = self.Image.height() - 1
|
|
left = 0
|
|
right = 0
|
|
for i in range(self.Image.width()):
|
|
if (self.IsColorBlack(self.Image.pixel(i, j)) and left == 0):
|
|
left = i
|
|
else:
|
|
if (left != 0 and self.IsColorBlack(self.Image.pixel(i, j))):
|
|
right = i
|
|
|
|
if (left and not right):
|
|
right = left
|
|
|
|
left -= 1
|
|
i = self.Image.width() - 1
|
|
top = 0
|
|
bot = 0
|
|
|
|
for j in range(self.Image.height()):
|
|
if (self.IsColorBlack(self.Image.pixel(i, j)) and top == 0):
|
|
top = j
|
|
else:
|
|
if (top and self.IsColorBlack(self.Image.pixel(i, j))):
|
|
bot = j
|
|
|
|
if (top and not bot):
|
|
bot = top
|
|
top -= 1
|
|
# print("GetContentArea left: %d top:%d %d %d" % (left, top, right - left, bot - top))
|
|
return (QRect(left, top, right - left, bot - top))
|
|
|
|
def GetResizeArea(self):
|
|
j = 0
|
|
left = 0
|
|
right = 0
|
|
|
|
for i in range(self.Image.width()):
|
|
if (self.IsColorBlack(self.Image.pixel(i, j)) and left == 0):
|
|
left = i
|
|
if (left and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack(
|
|
self.Image.pixel(i + 1, j))):
|
|
right = i
|
|
left -= 1
|
|
# print("ResizeDistancesX.append ", left, " ", right - left)
|
|
self.ResizeDistancesX.append((left, right - left))
|
|
right = 0
|
|
left = 0
|
|
i = 0
|
|
top = 0
|
|
bot = 0
|
|
|
|
for j in range(self.Image.height()):
|
|
if (self.IsColorBlack(self.Image.pixel(i, j)) and top == 0):
|
|
top = j
|
|
|
|
if (top and self.IsColorBlack(self.Image.pixel(i, j)) and not self.IsColorBlack(
|
|
self.Image.pixel(i, j + 1))):
|
|
bot = j
|
|
top -= 1
|
|
# print("ResizeDistancesY.append ", top, " ", bot - top)
|
|
self.ResizeDistancesY.append((top, bot - top))
|
|
top = 0
|
|
bot = 0
|
|
# print(self.ResizeDistancesX, len(self.ResizeDistancesX))
|
|
# print(self.ResizeDistancesY, len(self.ResizeDistancesY))
|
|
|
|
def GetFactor(self, width, height, factorX, factorY):
|
|
topResize = width - (self.Image.width() - 2)
|
|
leftResize = height - (self.Image.height() - 2)
|
|
for i in range(len(self.ResizeDistancesX)):
|
|
topResize += self.ResizeDistancesX[i][1]
|
|
factorX += self.ResizeDistancesX[i][1]
|
|
|
|
for i in range(len(self.ResizeDistancesY)):
|
|
leftResize += self.ResizeDistancesY[i][1]
|
|
factorY += self.ResizeDistancesY[i][1]
|
|
|
|
factorX = float(topResize) / factorX
|
|
factorY = float(leftResize) / factorY
|
|
return factorX, factorY
|
|
|
|
def UpdateCachedImage(self, width, height):
|
|
# print("UpdateCachedImage: ", width, " " , height)
|
|
self.CachedImage = QImage(
|
|
width, height, QImage.Format_ARGB32_Premultiplied)
|
|
self.CachedImage.fill(QColor(0, 0, 0, 0))
|
|
painter = QPainter(self.CachedImage)
|
|
factorX = 0.0
|
|
factorY = 0.0
|
|
factorX, factorY = self.GetFactor(width, height, factorX, factorY)
|
|
# print("after GetFactor: ", width, height, factorX, factorY)
|
|
lostX = 0.0
|
|
lostY = 0.0
|
|
x1 = 0 # for image parts X
|
|
y1 = 0 # for image parts Y
|
|
# widthResize # width for image parts
|
|
# heightResize # height for image parts
|
|
resizeX = 0
|
|
resizeY = 0
|
|
offsetX = 0
|
|
offsetY = 0
|
|
for i in range(len(self.ResizeDistancesX)):
|
|
y1 = 0
|
|
offsetY = 0
|
|
lostY = 0.0
|
|
for j in range(len(self.ResizeDistancesY)):
|
|
widthResize = self.ResizeDistancesX[i][0] - x1
|
|
heightResize = self.ResizeDistancesY[j][0] - y1
|
|
|
|
self.DrawConstPart(QRect(x1 + 1, y1 + 1, widthResize, heightResize),
|
|
QRect(x1 + offsetX, y1 + offsetY, widthResize, heightResize), painter)
|
|
|
|
y2 = self.ResizeDistancesY[j][0]
|
|
heightResize = self.ResizeDistancesY[j][1]
|
|
resizeY = round(float(heightResize) * factorY)
|
|
lostY += resizeY - (float(heightResize) * factorY)
|
|
if (fabs(lostY) >= 1.0):
|
|
if (lostY < 0):
|
|
resizeY += 1
|
|
lostY += 1.0
|
|
else:
|
|
resizeY -= 1
|
|
lostY -= 1.0
|
|
|
|
self.DrawScaledPart(QRect(x1 + 1, y2 + 1, widthResize, heightResize),
|
|
QRect(x1 + offsetX, y2 + offsetY, widthResize, resizeY), painter)
|
|
|
|
x2 = self.ResizeDistancesX[i][0]
|
|
widthResize = self.ResizeDistancesX[i][1]
|
|
heightResize = self.ResizeDistancesY[j][0] - y1
|
|
resizeX = round(float(widthResize) * factorX)
|
|
lostX += resizeX - (float(widthResize) * factorX)
|
|
if (fabs(lostX) >= 1.0):
|
|
if (lostX < 0):
|
|
resizeX += 1
|
|
lostX += 1.0
|
|
else:
|
|
resizeX -= 1
|
|
lostX -= 1.0
|
|
|
|
self.DrawScaledPart(QRect(x2 + 1, y1 + 1, widthResize, heightResize),
|
|
QRect(x2 + offsetX, y1 + offsetY, resizeX, heightResize), painter)
|
|
|
|
heightResize = self.ResizeDistancesY[j][1]
|
|
self.DrawScaledPart(QRect(x2 + 1, y2 + 1, widthResize, heightResize),
|
|
QRect(x2 + offsetX, y2 + offsetY, resizeX, resizeY), painter)
|
|
|
|
y1 = self.ResizeDistancesY[j][0] + self.ResizeDistancesY[j][1]
|
|
offsetY += resizeY - self.ResizeDistancesY[j][1]
|
|
|
|
x1 = self.ResizeDistancesX[i][0] + self.ResizeDistancesX[i][1]
|
|
offsetX += resizeX - self.ResizeDistancesX[i][1]
|
|
|
|
x1 = self.ResizeDistancesX[len(
|
|
self.ResizeDistancesX) - 1][0] + self.ResizeDistancesX[len(self.ResizeDistancesX) - 1][1]
|
|
widthResize = self.Image.width() - x1 - 2
|
|
y1 = 0
|
|
lostX = 0.0
|
|
lostY = 0.0
|
|
offsetY = 0
|
|
for i in range(len(self.ResizeDistancesY)):
|
|
self.DrawConstPart(QRect(x1 + 1, y1 + 1, widthResize, self.ResizeDistancesY[i][0] - y1),
|
|
QRect(x1 + offsetX, y1 + offsetY, widthResize,
|
|
self.ResizeDistancesY[i][0] - y1), painter)
|
|
y1 = self.ResizeDistancesY[i][0]
|
|
resizeY = round(float(self.ResizeDistancesY[i][1]) * factorY)
|
|
lostY += resizeY - (float(self.ResizeDistancesY[i][1]) * factorY)
|
|
if (fabs(lostY) >= 1.0):
|
|
if (lostY < 0):
|
|
resizeY += 1
|
|
lostY += 1.0
|
|
else:
|
|
resizeY -= 1
|
|
lostY -= 1.0
|
|
|
|
self.DrawScaledPart(QRect(x1 + 1, y1 + 1, widthResize, self.ResizeDistancesY[i][1]),
|
|
QRect(x1 + offsetX, y1 + offsetY, widthResize, resizeY), painter)
|
|
y1 = self.ResizeDistancesY[i][0] + self.ResizeDistancesY[i][1]
|
|
offsetY += resizeY - self.ResizeDistancesY[i][1]
|
|
|
|
y1 = self.ResizeDistancesY[len(
|
|
self.ResizeDistancesY) - 1][0] + self.ResizeDistancesY[len(self.ResizeDistancesY) - 1][1]
|
|
heightResize = self.Image.height() - y1 - 2
|
|
x1 = 0
|
|
offsetX = 0
|
|
for i in range(len(self.ResizeDistancesX)):
|
|
self.DrawConstPart(QRect(x1 + 1, y1 + 1, self.ResizeDistancesX[i][0] - x1, heightResize),
|
|
QRect(x1 + offsetX, y1 + offsetY, self.ResizeDistancesX[i][0] - x1,
|
|
heightResize), painter)
|
|
x1 = self.ResizeDistancesX[i][0]
|
|
resizeX = round(float(self.ResizeDistancesX[i][1]) * factorX)
|
|
lostX += resizeX - (float(self.ResizeDistancesX[i][1]) * factorX)
|
|
if (fabs(lostX) >= 1.0):
|
|
if (lostX < 0):
|
|
resizeX += 1
|
|
lostX += 1.0
|
|
else:
|
|
resizeX -= 1
|
|
lostX += 1.0
|
|
|
|
self.DrawScaledPart(QRect(x1 + 1, y1 + 1, self.ResizeDistancesX[i][1], heightResize),
|
|
QRect(x1 + offsetX, y1 + offsetY, resizeX, heightResize), painter)
|
|
x1 = self.ResizeDistancesX[i][0] + self.ResizeDistancesX[i][1]
|
|
offsetX += resizeX - self.ResizeDistancesX[i][1]
|
|
|
|
x1 = self.ResizeDistancesX[len(
|
|
self.ResizeDistancesX) - 1][0] + self.ResizeDistancesX[len(self.ResizeDistancesX) - 1][1]
|
|
widthResize = self.Image.width() - x1 - 2
|
|
y1 = self.ResizeDistancesY[len(
|
|
self.ResizeDistancesY) - 1][0] + self.ResizeDistancesY[len(self.ResizeDistancesY) - 1][1]
|
|
heightResize = self.Image.height() - y1 - 2
|
|
self.DrawConstPart(QRect(x1 + 1, y1 + 1, widthResize, heightResize),
|
|
QRect(x1 + offsetX, y1 + offsetY, widthResize, heightResize), painter)
|