Real world use cases
I understand that the full utility of PS BAM may not be apparent at first glance, and also that the particulars of the parameters and their options may not be overly intuitive. In fact, many seem to find using command-line utilities a rather daunting prospect in general. Because of this, I will demonstrate real use cases as they are requested or as I encounter them. As I have said before, if there is something you'd like PS BAM to do, just ask and I'll be happy to explain how it is done or do my best to implement the requested behavior. With that said, here are some examples:
2) Very Good (Hyper) Compression of BAM V1 Files
Background
I needed to add three new BAM files to the Thrown Hammers mod, and wanted to compress them as much as possible to save space and minimize download bandwidth. PS BAM can achieve very good compression at the cost of processing time.
Usage
I copied the three BAMs I wanted to compress to "C:\Users\Sam\Desktop\TH_Dark_Theme\" and ran the following:
"D:\Program Files\GitHub Projects\PS-BAM\PS BAM_x64.exe" --AdvancedFLTCompression 1 --AdvancedZlibCompress 2 --AllowShortPalette 1 --AlphaCutoff 10 --AutodetectPalettedBAM 1 --AutodetectPalettedThreshold 500 --BAMProfile "" --Compress 1 --DebugLevelL 1 --DebugLevelP 2 --DebugLevelS 1 --DropDuplicateFrameData 1 --DropDuplicateFrameEntries 1 --DropDuplicatePaletteEntries 1 --DropEmptyCycleEntries 1 --DropUnusedFrameData 1 --DropUnusedFrameEntries 1 --DropUnusedPaletteEntries 1 --ExtraTrimBuffer 2 --ExtraTrimDepth 3 --FindBestRLEIndex 0 --FixPaletteColorErrors 1 --FLTSanityCutoff 5040 --ForceShadowColor 0 --ForceTransColor 1 --IntelligentRLE 1 --MaxRLERun 255 --OrderOfOperations C --ReduceFrameColumnLT 1 --ReduceFramePixelLT 1 --ReduceFrameRowLT 1 --SearchTransColor 1 --TrimFrameData 1 --zopfliIterations 1000000 --LogFile "C:\Users\Sam\Desktop\TH_Dark_Theme\Log.txt" --OutPath "C:\Users\Sam\Desktop\TH_Dark_Theme\compressed" --Save "BAM" "C:\Users\Sam\Desktop\TH_Dark_Theme\*.bam"
So what the hell is all this? I'll go into some detail. There are multiple levels of indirection built into the BAM V1 file format. Many of the structures contain pointers to other "data", and because of this, duplicate or unused "data" can be removed without affecting the BAM in any visual manner (from the player's perspective in the game).
"D:\Program Files\GitHub Projects\PS-BAM\PS BAM_x64.exe"
The path to the PS BAM executable. Use the 64-bit one if you can (it should be a bit faster).
--AdvancedFLTCompression 1
From the
IESDP:
The frame lookup table is an array of frame indices. A cycle specifies a sequence of entries in this table. For instance, an animation might start at the 0th element in this array, and travel over 6 frames. (The next animation, then, would typically start at the 6th element and run over some number of indices.) If the first 6 entries in this table were { 0, 1, 1, 2, 3, 4 }, the animation would display frame #0, followed by frame #1 for two time periods, followed by frames 2, 3, 4 in order.
So let's say the 0th cycle uses frames [3, 4, 5, 6, 7, 8, 9] and the 1st cycle uses frames [0, 1, 2, 3, 4, 5]. These runs of frame indices are stored in the Frame Lookup Table (FLT for short). Each cycle entry is merely a pointer into the FLT and a count of how many frame entries to use. Typically, these runs of frame indices are merely stored end-to-end in the FLT like [3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5]. The 0th cycle entry would point to index 0 of the FLT and span 7 frame indices, and the 1st cycle entry would point to index 7 and span 6 frame entries. Because of this level of indirection, however, this need not be the case. Instead we can rearrange and overlap the runs of frame indices to make it shorter: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]. Now the 0th cycle entry would point to index 3 of the FLT and span 7 frame indices, and the 1st cycle entry would point to index 0 and span 6 frame entries. Now instead of our FLT being 13 frame indices, it is only 10. The problem is that there may be more than one possible way to combine multiple arrays of frame indices. Consider the following hypothetical situation:
You can see that one possible concatenation is better than the other. --AdvancedFLTCompression attempts to find the smallest possible concatenation of all arrays of frame indices. It does this by attempting to combine them in every possible order (every permutation). The issue we run into is that the number of possible permutations is equal to the factorial of the number of cycles. So if there are 5 cycles, 5!=120 possible permutations; 7 cycles is 5,040 permutations, 9!=362,888; for a creature BAM like AMOOG11 that has 54 cycles there are over 2x10^71 possible orders to attempt to combine the arrays of frame indices. Not even I have that kind of time, so
--FLTSanityCutoff
can be used to stop attempting different permutations after the specified number in the event things extrapolate out of control. PS BAM is actually a bit smarter than blindly attempting every permutation from the beginning. It starts by dropping any array (of frame indices) which is wholly contained within another array. It then removes any array that has no frame indices in common with any other arrays from the remaining pool. Finally the arrays are sorted by the average value of the frame indices in attempt to achieve better overlap with fewer permutations. Only after this does it start attempting to concatenate every permutation of the remaining pool. A good initial value for --FLTSanityCutoff is 120, but when I have the time I tend to use 720 or 5040. AdvancedFLTCompression is mathematically lossless.
--AdvancedZlibCompress
This applies z-lib compliant compression to compress a BAM V1 file to a BAMC V1 file. A value of 1 uses the actual z-lib dll on the highest setting to perform the compression. A value of 2 will perform the compression with zopfli which performs z-lib compliant compression, but is capable of better compression that the the original z-lib library itself. Additionally,
--zopfliIterations
specifies the number of iterations zopfli will perform searching for better compression. The more iterations, the better the compression but the longer it takes. 500 is ok for the casual user. 10,000 is more than enough. Anything higher than that might save you a couple of bytes, but will take a long time to complete. AdvancedzlibCompress is mathematically lossless.
--AllowShortPalette
After dropping unused and duplicate palette entries, this option will make the BAM's palette only big enough to hold all of the used colors (thus storing less than the 256 color, 1024 byte maximum). If this option is disabled, the palette will be padded with zeros (equating to pure black). The way PS BAM loads BAMs, AllowShortPalette itself should be mathematically lossless.
--AlphaCutoff
This used to transform nearly transparent pixels into the fully transparent background color, thus freeing up palette entries that can be dropped and potentially increasing how many transparent pixels can be trimmed from the frame. In BAMs, alpha values range from 1 (nearly transparent) to 255 (fully opaque) with 0 also representing fully opaque for backwards compatibility. With a value of, say 10, we are setting any color with an alpha value between 1 and 10 inclusive to the background (transparent) color. AlphaCutoff is visually lossy.
--AutodetectPalettedBAM
"Paletted" BAMs have a special palette that usually look like this:
Each of the special color gradients in this palette are remapped to other color gradients assigned by character or item colors (this is what the PaletteGenerator mentioned above simulates). If --AutodetectPalettedBAM is enabled, the sum of the Euclidean distance in RGBA colorspace is calculated between the BAM's palette and this special one. If a paletted BAM is detected, duplicate and unused palette entries will not be dropped form the palette.
--AutodetectPalettedThreshold
Provides the opportunity to set a threshold of what is and is not considered a "paletted" BAM. A value of 0 (zero) means the palettes must match exactly. A value of 500 will identify a BAM as paletted even if it uses the teal and pink transparent and shadow colors from BAMWorkshop. A handful of paletted BAMs in unmodded games use a similar but different palette:
A value of 14100 will identify vanilla off-paletted palettes as paletted (at least all the variants I tested), but it can be set to any number. These settings have no direct impact on lossless vs lossy compression; instead see the descriptions for --DropDuplicatePaletteEntries and --DropUnusedPaletteEntries.
--BAMProfile
This can be used to set some predefined compression profiles, but as the development of PS BAM progresses, these
profiles are subject to change. The current accepted profiles are Max, Recommended, Safe, Quick, and None. This is the only setting that is position dependent, meaning you can set a compression profile and then overwrite specific settings by specifying them individually after it. Additionally/alternately, you may specify a target game engine for some additional engine-specific toggling of settings (although this feature may not handle every engine's specific quirks under every circumstance). Accepted engine variants are
BG1,
PST,
IWD,
BG2,
IWD2, and
EE. As of 20181125, the current code looks like:
Arr:=StrSplit(Settings.CompressionProfile,A_Space)
Loop, % Arr.Length()
{
If (Arr[A_Index]="Recommended")
Settings.Compress:=1, Settings.FixPaletteColorErrors:=1, Settings.AutodetectPalettedBAM:=1, Settings.DropDuplicatePaletteEntries:=1, Settings.DropUnusedPaletteEntries:=1, Settings.SearchTransColor:=1, Settings.ForceTransColor:=1, Settings.ForceShadowColor:=0, Settings.AlphaCutoff:=10, Settings.AllowShortPalette:=1, Settings.TrimFrameData:=1, Settings.ExtraTrimBuffer:=2, Settings.ExtraTrimDepth:=3, Settings.ReduceFrameRowLT:=1, Settings.ReduceFrameColumnLT:=1, Settings.ReduceFramePixelLT:=1, Settings.DropDuplicateFrameData:=1, Settings.DropUnusedFrameData:=1, Settings.IntelligentRLE:=1, Settings.MaxRLERun:=255, Settings.FindBestRLEIndex:=0, Settings.DropDuplicateFrameEntries:=1, Settings.DropUnusedFrameEntries:=1, Settings.AdvancedFLTCompression:=1, Settings.FLTSanityCutoff:=720, Settings.DropEmptyCycleEntries:=1, Settings.AdvancedZlibCompress:=2, Settings.zopfliIterations:=1000
Else If (Arr[A_Index]="Max")
Settings.Compress:=1, Settings.FixPaletteColorErrors:=1, Settings.AutodetectPalettedBAM:=0, Settings.DropDuplicatePaletteEntries:=1, Settings.DropUnusedPaletteEntries:=1, Settings.SearchTransColor:=1, Settings.ForceTransColor:=1, Settings.AlphaCutoff:=10, Settings.AllowShortPalette:=1, Settings.TrimFrameData:=1, Settings.ExtraTrimBuffer:=2, Settings.ExtraTrimDepth:=3, Settings.ReduceFrameRowLT:=1, Settings.ReduceFrameColumnLT:=1, Settings.ReduceFramePixelLT:=1, Settings.DropDuplicateFrameData:=1, Settings.DropUnusedFrameData:=1, Settings.IntelligentRLE:=1, Settings.MaxRLERun:=255, Settings.FindBestRLEIndex:=1, Settings.DropDuplicateFrameEntries:=1, Settings.DropUnusedFrameEntries:=1, Settings.AdvancedFLTCompression:=1, Settings.FLTSanityCutoff:=5040, Settings.DropEmptyCycleEntries:=1, Settings.AdvancedZlibCompress:=2, Settings.zopfliIterations:=1000
Else If (Arr[A_Index]="Safe")
Settings.Compress:=1, Settings.FixPaletteColorErrors:=1, Settings.AutodetectPalettedBAM:=1, Settings.DropDuplicatePaletteEntries:=0, Settings.DropUnusedPaletteEntries:=0, Settings.SearchTransColor:=1, Settings.ForceTransColor:=0, Settings.ForceShadowColor:=0, Settings.AlphaCutoff:=10, Settings.AllowShortPalette:=0, Settings.TrimFrameData:=1, Settings.ExtraTrimBuffer:=0, Settings.ExtraTrimDepth:=0, Settings.ReduceFrameRowLT:=1, Settings.ReduceFrameColumnLT:=1, Settings.ReduceFramePixelLT:=1, Settings.DropDuplicateFrameData:=1, Settings.DropUnusedFrameData:=1, Settings.IntelligentRLE:=1, Settings.MaxRLERun:=254, Settings.FindBestRLEIndex:=0, Settings.DropDuplicateFrameEntries:=1, Settings.DropUnusedFrameEntries:=1, Settings.AdvancedFLTCompression:=1, Settings.FLTSanityCutoff:=720, Settings.AdvancedZlibCompress:=0
Else If (Arr[A_Index]="Quick")
Settings.Compress:=1, Settings.FixPaletteColorErrors:=1, Settings.AutodetectPalettedBAM:=1, Settings.DropDuplicatePaletteEntries:=1, Settings.DropUnusedPaletteEntries:=1, Settings.SearchTransColor:=1, Settings.ForceTransColor:=1, Settings.ForceShadowColor:=0, Settings.AlphaCutoff:=10, Settings.AllowShortPalette:=1, Settings.TrimFrameData:=1, Settings.ExtraTrimBuffer:=2, Settings.ExtraTrimDepth:=3, Settings.ReduceFrameRowLT:=1, Settings.ReduceFrameColumnLT:=1, Settings.ReduceFramePixelLT:=1, Settings.DropDuplicateFrameData:=1, Settings.DropUnusedFrameData:=1, Settings.IntelligentRLE:=1, Settings.MaxRLERun:=254, Settings.FindBestRLEIndex:=0, Settings.DropDuplicateFrameEntries:=1, Settings.DropUnusedFrameEntries:=1, Settings.AdvancedFLTCompression:=1, Settings.FLTSanityCutoff:=1, Settings.DropEmptyCycleEntries:=1, Settings.AdvancedZlibCompress:=1
Else If (Arr[A_Index]="None")
Settings.Compress:=0
; BG1 | PST | IWD | BG2 | IWD2 | EE
Else If (Arr[A_Index]="BG1") OR (Arr[A_Index]="PST")
{
Settings.AdvancedZlibCompress:=0
Settings.ForceTransColor:=1 ; May also want to include Settings.SearchTransColor:=1 here.
}
Else If (Arr[A_Index]="EE")
Settings.FindBestRLEIndex:=0
}
For example, you might use '--BAMProfile Quick' to enable the quick compression profile which is tailored to fast but not-as good compression. Or you might use '--BAMProfile "Max
EE"' to attempt to achieve the best possible (but much slower) compression while maintaining compatibility with the
EE engine. The scope of these engine-specific toggles may need to be expanded (I haven't looked at or used them in a while).
If you have additional questions or suggestions for changes to these defaults, just ask.
--Compress
This setting globally toggles compression as a whole on or off. In the near future I am strongly considering depreciating it and relying exclusively on the presence of "C" in --OrderOfOperations to enable/disable compression. As of 20181125, you must still use '--Compress 1 --OrderOfOperations "C"' to enable compression.
--DebugLevelL
--DebugLevelP
--DebugLevelS
This tells the program how much debug output to display. DebugLevelL is for the loading step, DebugLevelP is for the processing step (which includes compression), and DebugLevelS is for the saving step. Values of:
- -1 = very silent (no log)
- 0 = Silent (errors only)
- 1 = Normal (errors and warnings)
- 2 = Verbose (errors, warnings, extra info)
--LogFile
Specifies where to save this debug log. If a blank value is specified, e.g. '--LogFile ""', then no log will be saved. Otherwise it should be the path and filename of where to save the log file.
--DropDuplicateFrameData
This allows the BAM compressor to drop duplicate frame data. Because of the way PS BAM reads BAMs, this operation should be mathematically lossless.
--DropDuplicateFrameEntries
This allows the BAM compressor to drop duplicate frame entries. Because of the way PS BAM reads BAMs, this operation should be mathematically lossless.
--DropDuplicatePaletteEntries
This allows the BAM compressor to drop duplicate palette entries. This operation is visually lossless but should not be used with paletted BAMs.
--DropEmptyCycleEntries
This allows the BAM compressor to drop cycles which contain no frames. Depending on the type of BAM you are working with (item, spell, GUI, creature animation, etc.), this may or may not be a desirable thing to do and may or may not alter how the game engine uses or displays a BAM. Under some circumstances, this operation may be visually lossless, but it is not mathematically lossless.
--DropUnusedFrameData 1
This allows the BAM compressor to drop unused frame data. This operation is visually lossless.
--DropUnusedFrameEntries
This allows the BAM compressor to drop unused frame data. This operation is visually lossless.
--DropUnusedPaletteEntries
This allows the BAM compressor to drop unused palette entries. As with the other Boolean options, a value of 0=FALSE/OFF and 1=TRUE/ON. However, with --DropUnusedPaletteEntries you may instead pass it a value of 2 which will only drop unused palette entries from the end of the palette. This operation is visually lossless, but should not be used on paletted BAMs (except with a value of 2 which should probably be okay).
--ExtraTrimBuffer
--ExtraTrimDepth
These can enable special trimming of non-transparent pixels if the specified conditions are met. See
this post for more details and an example. This operation is visually lossy.
--IntelligentRLE
This option enables intelligent run-length-encoding of frame data. It is "intelligent RLE" for a number of reasons. First, RLE will never be applied to a frame unless doing so will decrease its total size. Next, RLE'd data may consist of runs of the RLEColorIndex of up to 255 pixels. However, BAMWorkshop (BWI) has a know bug where it can only handle runs of up to 254 pixels. To add support for this popular legacy BAM editor, I have provided the option
--MaxRLERun
which should generally be given a value of 254 (to maintain support for BWI) or 255 (to allow for maximum possible compression). However, even if you allow PS BAM to use runs of up to 255, it will only do so if it provides better compression than using 254 (again, to maximize compatibility with BWI which for many people may be a priority).
By default, the transparent color will be used as the RLE color. This behavior may optionally be modified using:
--FindBestRLEIndex
According to the actual BAM file format specifications, the palette entry to use for RLE need not necessarily be the same palette entry as the transparent color. Some engine variants like
BG1 handle this properly while others like
ToB and the EEs do not. I have made a post about it
here and a Beamdog
bug ticket hoping to get this behavior corrected in a future
EE patch. Turning on this option will allow PS BAM to search every used palette entry for the one that provides the best compression as the RLE color. RLE is always mathematically lossless.
--FixPaletteColorErrors
This will detect if the teal/cyan background color and pink shadow color from BAMWorkshop are present in the BAM's palette, and if so correct them to the proper green and black colors. It will also correct the brown background color and dark grey shadow color occasionally produced by BAM Batcher.
--SearchTransColor
Early engine variants sometimes allowed a palette entry other than the first to be the transparent color. From the
IESDP:
The transparency index is set to the first occurence of RGB(0,255,0). If RGB(0,255,0) does not exist in the palette then transparency index is set to 0
However later engine variants (
ToB and the EEs) make palette entry 0 the transparent color, regardless of what color is there. Some discussion on the subject starts in
this post. --SearchTransColor tells PS BAM to search the palette for the transparent color (green). If it doesn't find it, the transparent index will be set to palette entry 0.
--ForceTransColor
This setting tells PS BAM to force palette entry 0 to be the proper transparent color. If --SearchTransColor was enabled and the transparent color was found at a palette entry other than 0, it will move it to palette entry 0. Otherwise it will simply overwrite palette entry 0 with the transparent color (green).
--ForceShadowColor
This will force PS BAM to make the shadow color (palette entry 1) to be the proper black color. Depending on what value is passed to --ForceShadowColor determines how this is to be achieved. Available options are 0=None | 1=Force | 2=Move | 3=Insert (move will insert if fails). 0 will disable forcing the shadow color. 1 will overwrite palette entry 1 with the shadow color (black). 2 will search the palette for the shadow color (black) and move it to palette entry 1 if it finds it (or resort to method 3 if it does not). 3 will find an unused palette entry if there is one, move it to palette entry 0, and set it to the shadow color (black). If all palette entries are used, it will resort to method 1.
--OrderOfOperations
This is used to enable the distinct categories of processes that PS BAM can perform, and specifies the order in which to perform them. Parameters for this option are any ordering of P, C, and E where P=Process, C=Compress, and E=Export. For example if you used "--OrderOfOperations C" you would compress the BAM without performing any additional processing or exporting operations. Alternatively, if you used "--OrderOfOperations PCE" you would perform preliminary processing of the BAM, then compress it, then perform any requested export operations.
--ReduceFrameColumnLT
--ReduceFramePixelLT
--ReduceFrameRowLT
These options will reduce a frame with no more than the specified number of columns, pixels, or rows to a single transparent pixel at offset (0,0). For example "--ReduceFramePixelLT 1" would make any frame consisting of a single pixel (no matter the color) to a single transparent pixel located at horizontal and vertical offsets of 0. Use Case: Creature animations often contain many frames consisting of a single orange pixel which are not even displayed by the engine and manipulating them in this way will often improve overall compression. These options are at worst visually lossy.
--TrimFrameData
Trims rows and columns of transparent pixels from around a frame, manipulating it's offsets to compensate accordingly.
--OutPath
Give it the directory specifying where to put all the output files including exported frames, palettes, sequences, and any saved files like BAMs, BAMDs, GIFs, etc.
--Save
Used to specify in what format to save the processed/generated BAMs in. Accepted format are BAM, GIF, and BAMD where BAMD takes the filetype for each frame from Settings.ExportFrames. Blank is do not save (but exports will still be processed).
Very last should come the files to process. Wildcards are accepted, and any number of input files can be specified.
Result
These BAMs were quite simple (1 frame in 1 sequence) so they aren't a good example of just how much better this hyper compression can be, but here are the results:
Name OriginalSize UncompressedSize CompressedSize %OfOriginalSize %OfUncompressedSize Time
BDDHAM03.BAM 2374 9690 1887 79.486099 % 19.473684 % 1140.532352 sec.
BDDHAM04.BAM 2589 9440 1930 74.546157 % 20.444915 % 1037.944160 sec.
CHAMM07.BAM 4019 16906 3577 89.002239 % 21.158169 % 1348.988349 sec.
The hyper compressed BAMs were about 81% of the size NearInfinity produced and 20% compared to being fully uncompressed. The times are artificially high because I ran a stupidly high number (1,000,000) of zopfli iterations (z-lib compliant compression to a BAMC file), which probably only saved a handful of bytes compared to say 500 iterations. That's just my style, tho.
Edited by Sam., 25 November 2018 - 05:21 PM.