You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
colorsafe/colorsafe/csdatastructures.py

236 lines
6.7 KiB

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