236 lines
6.7 KiB
Python
236 lines
6.7 KiB
Python
import math
|
|
import constants
|
|
import utils
|
|
|
|
|
|
class ColorChannels:
|
|
"""A group of color channels consisting of Red, Green, and Blue values from 0.0 to 1.0.
|
|
|
|
TODO: Channels and values (colors and shades) should be stored in metadata header separately.
|
|
TODO: Cmd program can simplify color/shade combos with Grayscale, CYMK, RGB, and higher options.
|
|
|
|
"""
|
|
RedDefault = 0.0
|
|
GreenDefault = 0.0
|
|
BlueDefault = 0.0
|
|
|
|
def __init__(self, red=RedDefault, green=GreenDefault, blue=BlueDefault):
|
|
self.red = red
|
|
self.green = green
|
|
self.blue = blue
|
|
|
|
def setChannels(self, channels):
|
|
if len(channels) == constants.ColorChannels:
|
|
self.red = channels[0]
|
|
self.green = channels[1]
|
|
self.blue = channels[2]
|
|
elif len(channels) == constants.ColorChannels1:
|
|
self.red = channels[0]
|
|
self.green = channels[0]
|
|
self.blue = channels[0]
|
|
|
|
def multiplyShade(self, shades):
|
|
if len(shades) == constants.ColorChannels:
|
|
self.red *= shades[0]
|
|
self.green *= shades[1]
|
|
self.blue *= shades[2]
|
|
elif len(shades) == constants.ColorChannels1:
|
|
self.red *= shades[0]
|
|
self.green *= shades[0]
|
|
self.blue *= shades[0]
|
|
|
|
def subtractShade(self, shade):
|
|
self.red -= shade
|
|
self.green -= shade
|
|
self.blue -= shade
|
|
|
|
def getChannels(self):
|
|
return (self.red, self.green, self.blue)
|
|
|
|
def getAverageShade(self):
|
|
return (self.red + self.green + self.blue) / constants.ColorChannels
|
|
|
|
|
|
class Dot:
|
|
"""A group of channels representing a group of colorDepth bits.
|
|
"""
|
|
|
|
channels = None
|
|
|
|
def getChannelNum(self, bitCount):
|
|
"""Get channel number based on how many bits (based on colorDepth) are needed. Currently, this maps 1:1.
|
|
"""
|
|
# TODO: Make these modes options for any colorDepth, add to metadata
|
|
# header.
|
|
if bitCount % constants.ColorChannels == 0:
|
|
channelNum = constants.ColorChannels
|
|
elif bitCount % constants.ColorChannels2 == 0:
|
|
channelNum = constants.ColorChannels2
|
|
else:
|
|
channelNum = constants.ColorChannels1
|
|
|
|
return channelNum
|
|
|
|
def getChannels(self):
|
|
return self.channels.getChannels()
|
|
|
|
|
|
class DotByte:
|
|
"""A group of 8 Dots, representing colorDepth bytes of data.
|
|
"""
|
|
|
|
dots = None
|
|
bytesList = None
|
|
|
|
|
|
class DotRow:
|
|
"""A horizontal group of DotBytes.
|
|
"""
|
|
|
|
dotBytes = None
|
|
bytesList = None
|
|
|
|
@staticmethod
|
|
def getMaxRowBytes(colorDepth, width):
|
|
return colorDepth * width / constants.ByteSize
|
|
|
|
@staticmethod
|
|
def getMagicRowBytes(colorDepth, width):
|
|
maxRowBytes = DotRow.getMaxRowBytes(colorDepth, width)
|
|
return [constants.MagicByte] * maxRowBytes
|
|
|
|
@staticmethod
|
|
def getXORMask(rowNumber):
|
|
return constants.Byte55 if rowNumber % 2 == 0 else constants.ByteAA
|
|
|
|
|
|
class Sector:
|
|
"""A vertical group of DotRows.
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_block_sizes(height, width, color_depth, ecc_rate):
|
|
def fill_block_sizes(bytes, max_bytes_per_block):
|
|
if bytes <= max_bytes_per_block:
|
|
return [bytes]
|
|
else:
|
|
block_sizes = [max_bytes_per_block] * (bytes / max_bytes_per_block)
|
|
|
|
remainder = bytes % max_bytes_per_block
|
|
if remainder != 0:
|
|
last_two_avg = utils.average([remainder, max_bytes_per_block])
|
|
block_sizes[-1] = int(math.ceil(last_two_avg))
|
|
block_sizes.append(int(math.floor(last_two_avg)))
|
|
|
|
return block_sizes
|
|
|
|
data_row_count = Sector.getDataRowCount(height, ecc_rate)
|
|
ecc_row_count = height - constants.MagicRowHeight - data_row_count
|
|
|
|
total_bytes = (data_row_count + ecc_row_count) * width * color_depth / constants.ByteSize
|
|
data_bytes = data_row_count * width * color_depth / constants.ByteSize
|
|
ecc_bytes = ecc_row_count * width * color_depth / constants.ByteSize
|
|
|
|
data_row_percentage = float(data_row_count) / (height - constants.MagicRowHeight)
|
|
ecc_row_percentage = float(ecc_row_count) / (height - constants.MagicRowHeight)
|
|
|
|
max_data_block_size = int(round(data_row_percentage * constants.RSBlockSizeMax))
|
|
max_ecc_block_size = int(round(ecc_row_percentage * constants.RSBlockSizeMax))
|
|
|
|
rs_block_sizes = fill_block_sizes(total_bytes, constants.RSBlockSizeMax)
|
|
data_block_sizes = fill_block_sizes(data_bytes, max_data_block_size)
|
|
ecc_block_sizes = fill_block_sizes(ecc_bytes, max_ecc_block_size)
|
|
|
|
return data_row_count, ecc_row_count, rs_block_sizes, data_block_sizes, ecc_block_sizes
|
|
|
|
@staticmethod
|
|
def getDataRowCount(height, eccRate):
|
|
return int(math.floor(
|
|
(height - constants.MagicRowHeight) / (1 + eccRate)))
|
|
|
|
|
|
class Metadata:
|
|
eccMode = "ECC"
|
|
dataMode = "DAT"
|
|
pageNumber = "PAG"
|
|
metadataCount = "MET"
|
|
|
|
ambiguous = "AMB"
|
|
crc32CCheck = "CRC"
|
|
csCreationTime = "TIM"
|
|
eccRate = "ECR"
|
|
fileExtension = "EXT"
|
|
fileSize = "SIZ"
|
|
filename = "NAM"
|
|
majorVersion = "MAJ"
|
|
minorVersion = "MIN"
|
|
revisionVersion = "REV"
|
|
totalPages = "TOT"
|
|
|
|
# Required, in order
|
|
RequiredInOrder = (eccMode, dataMode, pageNumber, metadataCount)
|
|
|
|
# Required, in no order
|
|
# TODO: Some of these should possibly be required on each page
|
|
RequiredNoOrder = (
|
|
ambiguous,
|
|
crc32CCheck,
|
|
eccRate,
|
|
majorVersion,
|
|
minorVersion,
|
|
revisionVersion,
|
|
fileSize,
|
|
csCreationTime,
|
|
totalPages,
|
|
fileExtension,
|
|
filename)
|
|
|
|
# TODO: Filename/ext not required, how to track?
|
|
|
|
|
|
class MetadataSector(Sector):
|
|
MetadataInitPaddingBytes = 1
|
|
ColorDepthBytes = 1
|
|
MetadataSchemeBytes = 3
|
|
MetadataEndPaddingBytes = 1
|
|
MetadataDefaultScheme = 1
|
|
|
|
metadata = None
|
|
data = None
|
|
|
|
height = None
|
|
width = None
|
|
colorDepth = None
|
|
eccRate = None
|
|
dataStart = None
|
|
|
|
|
|
class Page:
|
|
"""A collection of sectors, used for shuffling and placing them correctly on each page, and for keeping track of
|
|
page specific properties, like page number.
|
|
"""
|
|
|
|
sectors = None
|
|
|
|
dataSectors = None
|
|
metadataSectors = None
|
|
pageNumber = None
|
|
sectorsVertical = None
|
|
sectorsHorizontal = None
|
|
colorDepth = None
|
|
eccRate = None
|
|
sectorHeight = None
|
|
sectorWidth = None
|
|
|
|
|
|
class ColorSafeFile:
|
|
"""The ColorSafe data and borders, all dimensions in dots."""
|
|
|
|
data = None
|
|
|
|
|
|
class ColorSafeImages:
|
|
"""A collection of saved ColorSafeFile objects, as images of working regions without outside borders or headers
|
|
"""
|
|
|
|
images = None |