-
-
Save MrPowerGamerBR/be3106210edb603db9b0b5d6816eb931 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.perfectdreams.discordbanner.gifs | |
import java.awt.Color | |
import java.awt.image.BufferedImage | |
import java.awt.image.DataBufferByte | |
import java.io.IOException | |
import java.io.OutputStream | |
import kotlin.experimental.and | |
/** | |
* Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more | |
* frames. | |
* | |
* <pre> | |
* Example: | |
* AnimatedGifEncoder e = new AnimatedGifEncoder(); | |
* e.start(outputFileName); | |
* e.setDelay(1000); // 1 frame per sec | |
* e.addFrame(image1); | |
* e.addFrame(image2); | |
* e.finish(); | |
</pre> * | |
* | |
* No copyright asserted on the source code of this class. May be used for any | |
* purpose, however, refer to the Unisys LZW patent for restrictions on use of | |
* the associated LZWEncoder class. Please forward any corrections to | |
* kweiner@fmsware.com. | |
* | |
* @author Kevin Weiner, FM Software | |
* @version 1.03 November 2003 | |
*/ | |
class AnimatedGifEncoder( | |
private val out: OutputStream, | |
) { | |
protected var width // image size | |
= 0 | |
protected var height = 0 | |
var transparent: Color? = null // transparent color if given | |
protected var transIndex // transparent index in color table | |
= 0 | |
var repeat = -1 // no repeat | |
var delay = 0 // frame delay (hundredths) | |
protected var started = false // ready to output frames | |
protected var image // current frame | |
: BufferedImage? = null | |
protected var pixels // BGR byte array from frame | |
: ByteArray? = null | |
protected var indexedPixels // converted frame indexed to palette | |
: ByteArray? = null | |
protected var colorDepth // number of bit planes | |
= 0 | |
protected var colorTab // RGB palette | |
: ByteArray? = null | |
protected var usedEntry = BooleanArray(256) // active palette entries | |
protected var palSize = 7 // color table size (bits-1) | |
protected var dispose = -1 // disposal code (-1 = use default) | |
protected var closeStream = false // close stream when finished | |
protected var firstFrame = true | |
protected var sizeSet = false // if false, get size from first frame | |
protected var sample = 1 // default sample interval for quantizer | |
/** | |
* Adds next GIF frame. The frame is not written immediately, but is actually | |
* deferred until the next frame is received so that timing data can be | |
* inserted. Invoking `finish()` flushes all frames. If | |
* `setSize` was not invoked, the size of the first image is used | |
* for all subsequent frames. | |
* | |
* @param im | |
* BufferedImage containing frame to write. | |
* @return true if successful. | |
*/ | |
fun addFrame(im: BufferedImage?, frameDelay: Int = delay, xPosition: Int = 0, yPosition: Int = 0): Boolean { | |
if (im == null || !started) { | |
return false | |
} | |
var ok = true | |
try { | |
if (!sizeSet) { | |
// use first frame's size | |
setSize(im.width, im.height) | |
} | |
image = im | |
convertImagePixels(im, im.width, im.height) // convert to correct format if necessary | |
analyzePixels() // build color table & map pixels | |
if (firstFrame) { | |
// Because this is the first frame, we should NOT write the hacky north-east coords | |
writeLSD() // logical screen descriptior | |
writePalette() // global color table | |
if (repeat >= 0) { | |
// use NS app extension to indicate reps | |
writeNetscapeExt() | |
} | |
} | |
writeGraphicCtrlExt(frameDelay) // write graphic control extension | |
writeImageDesc(im.width, im.height, xPosition, yPosition) // image descriptor | |
if (!firstFrame) { | |
writePalette() // local color table | |
} | |
writePixels(im.width, im.height) // encode and write pixel data | |
firstFrame = false | |
} catch (e: IOException) { | |
e.printStackTrace() | |
ok = false | |
} | |
return ok | |
} | |
/** | |
* Flushes any pending data and closes output file. If writing to an | |
* OutputStream, the stream is not closed. | |
*/ | |
fun finish(): Boolean { | |
if (!started) return false | |
var ok = true | |
started = false | |
try { | |
out!!.write(0x3b) // gif trailer | |
out!!.flush() | |
if (closeStream) { | |
out!!.close() | |
} | |
} catch (e: IOException) { | |
e.printStackTrace() | |
ok = false | |
} | |
// reset for subsequent use | |
transIndex = 0 | |
out.close() | |
// out = null | |
image = null | |
pixels = null | |
indexedPixels = null | |
colorTab = null | |
closeStream = false | |
firstFrame = true | |
return ok | |
} | |
/** | |
* Sets quality of color quantization (conversion of images to the maximum 256 | |
* colors allowed by the GIF specification). Lower values (minimum = 1) | |
* produce better colors, but slow processing significantly. 10 is the | |
* default, and produces good color mapping at reasonable speeds. Values | |
* greater than 20 do not yield significant improvements in speed. | |
* | |
* @param quality | |
* int greater than 0. | |
* @return | |
*/ | |
fun setQuality(quality: Int) { | |
var quality = quality | |
if (quality < 1) quality = 1 | |
sample = quality | |
} | |
/** | |
* Sets the GIF frame size. The default size is the size of the first frame | |
* added if this method is not invoked. | |
* | |
* @param w | |
* int frame width. | |
* @param h | |
* int frame width. | |
*/ | |
fun setSize(w: Int, h: Int) { | |
if (started && !firstFrame) return | |
width = w | |
height = h | |
if (width < 1) width = 320 | |
if (height < 1) height = 240 | |
sizeSet = true | |
} | |
/** | |
* Initiates GIF file creation on the given stream. The stream is not closed | |
* automatically. | |
* | |
* @param os | |
* OutputStream on which GIF images are written. | |
* @return false if initial write failed. | |
*/ | |
fun start(): Boolean { | |
var ok = true | |
closeStream = false | |
try { | |
writeString("GIF89a") // header | |
} catch (e: IOException) { | |
ok = false | |
} | |
return ok.also { started = it } | |
} | |
/** | |
* Analyzes image colors and creates color map. | |
*/ | |
protected fun analyzePixels() { | |
val len = pixels!!.size | |
val nPix = len / 3 | |
val transparent = transparent | |
// Check if the current frame has transparent pixels | |
var hasTransparentPixels = false | |
if (transparent != null) { | |
for (i in 0 until pixels!!.size step 3) { | |
val b = pixels!![i].toInt() and 0xff | |
val g = pixels!![i + 1].toInt() and 0xff | |
val r = pixels!![i + 2].toInt() and 0xff | |
if (r == transparent.red && g == transparent.green && b == transparent.blue) { | |
hasTransparentPixels = true | |
break | |
} | |
} | |
} | |
indexedPixels = ByteArray(nPix) | |
val nq = NeuQuant(pixels!!, len, sample) | |
// initialize quantizer | |
colorTab = nq.process() // create reduced palette | |
// convert map from BGR to RGB | |
run { | |
var i: Int = 0 | |
while (i < colorTab!!.size) { | |
val temp: Byte = colorTab!!.get(i) | |
colorTab!![i] = colorTab!!.get(i + 2) | |
colorTab!![i + 2] = temp | |
usedEntry[i / 3] = false | |
i += 3 | |
} | |
} | |
// map image pixels to new palette | |
var k = 0 | |
for (i in 0 until nPix) { | |
val index = nq.map(pixels!![k++].toInt() and 0xff, pixels!![k++].toInt() and 0xff, pixels!![k++].toInt() and 0xff) | |
usedEntry[index] = true | |
indexedPixels!![i] = index.toByte() | |
} | |
pixels = null | |
colorDepth = 8 | |
palSize = 7 | |
// Get closest match to transparent color if specified and if the current frame has transparent pixels | |
// We check if they are present to avoid issues if the frame only has a solid color without any transparency | |
if (hasTransparentPixels) { | |
transIndex = findClosest(transparent!!) | |
} else { | |
transIndex = -1 // reset transparent index to avoid issues (magic value) | |
} | |
} | |
/** | |
* Returns index of palette color closest to c | |
* | |
*/ | |
protected fun findClosest(c: Color): Int { | |
if (colorTab == null) return -1 | |
val r = c.red | |
val g = c.green | |
val b = c.blue | |
var minpos = 0 | |
var dmin = 256 * 256 * 256 | |
val len = colorTab!!.size | |
var i = 0 | |
while (i < len) { | |
val dr: Int = r - (colorTab!![i++].toInt() and 0xff) | |
val dg: Int = g - (colorTab!![i++].toInt() and 0xff) | |
val db: Int = b - (colorTab!![i].toInt() and 0xff) | |
val d = dr * dr + dg * dg + db * db | |
val index = i / 3 | |
if (usedEntry[index] && d < dmin) { | |
dmin = d | |
minpos = index | |
} | |
i++ | |
} | |
return minpos | |
}// create new image with right size/format | |
/** | |
* Extracts image pixels into byte array "pixels" | |
*/ | |
protected fun convertImagePixels(image: BufferedImage, width: Int, height: Int) { | |
val w = image.width | |
val h = image.height | |
val type = image.type | |
val tempImage = if (w != width || h != height || type != BufferedImage.TYPE_3BYTE_BGR) { | |
// create new image with right size/format | |
val temp = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR) | |
val g = temp.createGraphics() | |
g.drawImage(image, 0, 0, null) | |
temp | |
} else image | |
// println("Width: ${tempImage.width}; Height: ${tempImage.height}") | |
pixels = (tempImage.raster.dataBuffer as DataBufferByte).data | |
} | |
/** | |
* Writes Graphic Control Extension | |
*/ | |
@Throws(IOException::class) | |
protected fun writeGraphicCtrlExt(frameDelay: Int = delay) { | |
out.write(0x21) // extension introducer | |
out.write(0xf9) // GCE label | |
out.write(4) // data block size | |
val transp: Int | |
var disp: Int | |
if (transparent == null) { | |
transp = 0 | |
disp = 0 // dispose = no action | |
} else { | |
transp = if (transIndex != -1) 1 else 0 // If the trans index is our magic value, then it means that we do not have transparency on this frame! | |
// TODO: Allow customizing the dispose method | |
disp = 0 // do not dispose (original: force clear if using transparent color) | |
} | |
if (dispose >= 0) { | |
disp = dispose and 7 // user override | |
} | |
disp = disp shl 2 | |
// packed fields | |
out!!.write( | |
0 or // 1:3 reserved | |
disp or // 4:6 disposal | |
0 or // 7 user input - 0 = none | |
transp | |
) // 8 transparency flag | |
writeShort(frameDelay) // delay x 1/100 sec | |
out!!.write(transIndex) // transparent color index | |
out!!.write(0) // block terminator | |
} | |
/** | |
* Writes Image Descriptor | |
*/ | |
@Throws(IOException::class) | |
protected fun writeImageDesc(width: Int, height: Int, xPosition: Int, yPosition: Int) { | |
out!!.write(0x2c) // image separator | |
// image position x,y = 0,0 | |
if (firstFrame) { // The first frame NEEDS to have coordinates 0, 0, if not the animation won't play! | |
writeShort(0) | |
writeShort(0) | |
} else { | |
writeShort(xPosition) | |
writeShort(yPosition) | |
} | |
writeShort(width) // image size | |
writeShort(height) | |
// packed fields | |
if (firstFrame) { | |
// no LCT - GCT is used for first (or only) frame | |
out!!.write(0) | |
} else { | |
// specify normal LCT | |
out!!.write( | |
(0x80 or // 1 local color table 1=yes | |
0 or // 2 interlace - 0=no | |
0 or // 3 sorted - 0=no | |
0 or // 4-5 reserved | |
palSize) | |
) // 6-8 size of color table | |
} | |
} | |
/** | |
* Writes Logical Screen Descriptor | |
*/ | |
@Throws(IOException::class) | |
protected fun writeLSD() { | |
// logical screen size | |
writeShort(width) | |
writeShort(height) | |
// packed fields | |
out!!.write( | |
((0x80 or // 1 : global color table flag = 1 (gct used) | |
0x70 or // 2-4 : color resolution = 7 | |
0x00 or // 5 : gct sort flag = 0 | |
palSize)) | |
) // 6-8 : gct size | |
out!!.write(0) // background color index | |
out!!.write(0) // pixel aspect ratio - assume 1:1 | |
} | |
/** | |
* Writes Netscape application extension to define repeat count. | |
*/ | |
@Throws(IOException::class) | |
protected fun writeNetscapeExt() { | |
out!!.write(0x21) // extension introducer | |
out!!.write(0xff) // app extension label | |
out!!.write(11) // block size | |
writeString("NETSCAPE" + "2.0") // app id + auth code | |
out!!.write(3) // sub-block size | |
out!!.write(1) // loop sub-block id | |
writeShort(repeat) // loop count (extra iterations, 0=repeat forever) | |
out!!.write(0) // block terminator | |
} | |
/** | |
* Writes color table | |
*/ | |
@Throws(IOException::class) | |
protected fun writePalette() { | |
out!!.write(colorTab, 0, colorTab!!.size) | |
val n = (3 * 256) - colorTab!!.size | |
for (i in 0 until n) { | |
out!!.write(0) | |
} | |
} | |
/** | |
* Encodes and writes pixel data | |
*/ | |
@Throws(IOException::class) | |
protected fun writePixels(width: Int, height: Int) { | |
val encoder = LZWEncoder(width, height, (indexedPixels)!!, colorDepth) | |
encoder.encode((out)!!) | |
} | |
/** | |
* Write 16-bit value to output stream, LSB first | |
*/ | |
@Throws(IOException::class) | |
protected fun writeShort(value: Int) { | |
out!!.write(value and 0xff) | |
out!!.write((value shr 8) and 0xff) | |
} | |
/** | |
* Writes string to output stream | |
*/ | |
@Throws(IOException::class) | |
protected fun writeString(s: String) { | |
for (i in 0 until s.length) { | |
out.write(s[i].toInt()) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.perfectdreams.discordbanner.gifs | |
import net.perfectdreams.discordbanner.DiscordBannerGenerator | |
import java.awt.Color | |
import java.awt.image.BufferedImage | |
import java.io.File | |
import java.io.FileOutputStream | |
import java.io.IOException | |
import java.io.OutputStream | |
import javax.imageio.IIOException | |
import javax.imageio.ImageIO | |
import javax.imageio.stream.FileImageOutputStream | |
class GIFOverdrive( | |
val outputStream: OutputStream, | |
val frames: MutableList<DiscordBannerGenerator.MemoryFrame>, | |
val delay: Int = 3 | |
) { | |
private val TRANSPARENT_COLOR = Color(255, 0, 255) | |
private val DELAY = delay | |
private val DROP_FRAME_PIXEL_CHECK_THRESHOLD = 1.0 // original: 1.0 | |
// we decrease this to avoid dropping colors that shouldn't be dropped | |
private val DROP_PIXEL_SIMILAR_DISTANCE = 1 // original: 3 | |
fun convertFrames() { | |
println("GIFOverdrive!") | |
println("Frames: ${frames.size}") | |
println("Joining to a gifFrames list...") | |
val gifFrames = frames.map { GIFFrame(it.image, DELAY, it.dropPixelSimilarDistanceOverride) }.toMutableList() | |
val firstFrame = gifFrames.first().image | |
val width = firstFrame.width | |
val height = firstFrame.height | |
val pixelCount = width * height | |
val droppedDuplicatedFrames = mutableListOf<GIFFrame>(gifFrames.first()) // the first is always skipped | |
println("Dropping duplicate frames...") | |
for (index in gifFrames.indices) { | |
val previousFrame = gifFrames.getOrNull(index - 1) ?: continue | |
val currentFrame = gifFrames.getOrNull(index) ?: break | |
val previousFrameImage = previousFrame.image | |
val currentFrameImage = currentFrame.image | |
val intermediaryFrame = BufferedImage(currentFrame.image.width, currentFrame.image.height, BufferedImage.TYPE_INT_ARGB) | |
var equalPixelCount = 0 | |
for (x in 0 until intermediaryFrame.width) { | |
for (y in 0 until intermediaryFrame.height) { | |
if (previousFrameImage.getRGB(x, y) == currentFrameImage.getRGB(x, y)) { | |
equalPixelCount++ | |
} | |
} | |
} | |
val equalFramesPercentage = (equalPixelCount.toDouble() / pixelCount) | |
println("Equal Pixels for Frame $index: $equalPixelCount (${equalFramesPercentage * 100})") | |
if (equalFramesPercentage >= DROP_FRAME_PIXEL_CHECK_THRESHOLD) { // 450*180 | |
println("Current frame $index pixel count is equal to the previous frame, dropping current frame...") | |
droppedDuplicatedFrames.last().delay += DELAY | |
} else { | |
droppedDuplicatedFrames.add(currentFrame) | |
} | |
} | |
println("Calculating intermediary frames...") | |
val intermediaryFrames = mutableListOf<IntermediaryGIFFrame>( | |
IntermediaryGIFFrame( | |
droppedDuplicatedFrames.first().image, | |
droppedDuplicatedFrames.first().delay, | |
droppedDuplicatedFrames.first().image.width, | |
droppedDuplicatedFrames.first().image.height | |
) | |
) // The first will always exist | |
for (index in droppedDuplicatedFrames.indices.drop(1)) { | |
println("Writing intermediary frames for $index...") | |
val previousFrame = droppedDuplicatedFrames[index - 1] // will never be null! | |
val currentFrame = droppedDuplicatedFrames[index] | |
val previousFrameImage = previousFrame.image | |
val currentFrameImage = currentFrame.image | |
val intermediaryFrame = BufferedImage(currentFrame.image.width, currentFrame.image.height, BufferedImage.TYPE_INT_ARGB) | |
val graphics = intermediaryFrame.createGraphics() | |
graphics.color = TRANSPARENT_COLOR | |
graphics.fillRect(0, 0, width, height) | |
for (x in 0 until intermediaryFrame.width) { | |
for (y in 0 until intermediaryFrame.height) { | |
val previousRgb = previousFrameImage.getRGB(x, y) | |
val currentRgb = currentFrameImage.getRGB(x, y) | |
if (previousRgb != currentRgb) { | |
val previousColor = Color(previousRgb) | |
val newColor = Color(currentRgb) | |
// TODO: Remove this, this is only a workaround because the TS1 recording is too "dirty" to be used in a GIF | |
val similarDistance = currentFrame.dropPixelSimilarDistanceOverride ?: DROP_PIXEL_SIMILAR_DISTANCE | |
if ( | |
previousColor.red in (newColor.red - similarDistance)..(newColor.red + similarDistance) && | |
previousColor.green in (newColor.green - similarDistance)..(newColor.green + similarDistance) && | |
previousColor.blue in (newColor.blue - similarDistance)..(newColor.blue + similarDistance) | |
) {} else { | |
intermediaryFrame.setRGB(x, y, currentFrameImage.getRGB(x, y)) | |
} | |
} | |
} | |
} | |
var topXPosition: Int? = null | |
var topYPosition: Int? = null | |
var bottomXPosition: Int? = null | |
var bottomYPosition: Int? = null | |
// Find the top x/top y | |
for (x in 0 until intermediaryFrame.width) { | |
for (y in 0 until intermediaryFrame.height) { | |
val color = Color(intermediaryFrame.getRGB(x, y)) | |
if (color != TRANSPARENT_COLOR) { | |
if (topXPosition == null || topXPosition > x) | |
topXPosition = x | |
if (topYPosition == null || topYPosition > y) | |
topYPosition = y | |
} | |
if (color != TRANSPARENT_COLOR) { | |
if (bottomXPosition == null || x > bottomXPosition) | |
bottomXPosition = x | |
if (bottomYPosition == null || y > bottomYPosition) | |
bottomYPosition = y | |
} | |
} | |
} | |
val finalTopXPosition = topXPosition ?: 0 | |
val finalTopYPosition = topYPosition ?: 0 | |
val finalBottomXPosition = (bottomXPosition ?: 0) + 1 | |
val finalBottomYPosition = (bottomYPosition ?: 0) + 1 | |
println("Top X Position: $finalTopXPosition; Top Y Position: $finalTopYPosition") | |
println("Bottom X Position: $finalBottomXPosition; Bottom Y Position: $finalBottomYPosition") | |
val newWidth = finalBottomXPosition - finalTopXPosition | |
val newHeight = finalBottomYPosition - finalTopYPosition | |
println("Frame $index will have its content pasted from ($finalTopXPosition, $finalTopYPosition) with ($newWidth, $newHeight) to ($finalTopXPosition, $finalTopYPosition)") | |
val subImage = intermediaryFrame.getSubimage(finalTopXPosition, finalTopYPosition, newWidth, newHeight) | |
intermediaryFrames.add( | |
IntermediaryGIFFrame( | |
subImage, | |
currentFrame.delay, | |
finalTopXPosition, | |
finalTopYPosition | |
) | |
) | |
// graphics.color = Color.RED | |
// graphics.drawString("Frame $index; Top: $finalTopXPosition, $finalTopYPosition; Bottom: $finalBottomXPosition, $finalBottomYPosition", 80, 80) | |
// graphics.color = Color(20, 200, 255, 100) | |
// graphics.fillRect(finalTopXPosition, finalTopYPosition, newWidth, newHeight) | |
// write intermediary | |
ImageIO.write( | |
intermediaryFrame, | |
"png", | |
File("L:\\RandomProjects\\DiscordBanner\\intermediary\\$index.png") | |
) | |
} | |
println("Rendering GIF...") | |
if (false) { | |
for ((index, frame) in intermediaryFrames.filter { it.delay != 0 }.withIndex()) { // no need to render frames that have delay = 0 | |
println("Frame $index/${intermediaryFrames.size}: ${frame.image.width}x${frame.image.height}") | |
val outputStreamGif = FileOutputStream(File("L:\\RandomProjects\\DiscordBanner\\encoded_gif_frames\\$index.gif")) | |
val encoder = AnimatedGifEncoder(outputStreamGif) | |
encoder.delay = DELAY | |
encoder.transparent = TRANSPARENT_COLOR | |
encoder.repeat = 0 | |
encoder.start() | |
encoder.addFrame( | |
frame.image, | |
frame.delay, | |
frame.xPosition, | |
frame.yPosition | |
) | |
val outputStream = outputStreamGif | |
encoder.finish() | |
} | |
// encoder.finish() | |
} else { | |
val encoder = AnimatedGifEncoder(outputStream) | |
encoder.delay = DELAY | |
encoder.transparent = TRANSPARENT_COLOR | |
encoder.repeat = 0 | |
encoder.start() | |
var previousSize = 0L | |
for ((index, frame) in intermediaryFrames.filter { it.delay != 0 }.withIndex()) { // no need to render frames that have delay = 0 | |
println("Frame $index/${intermediaryFrames.size}: ${frame.image.width}x${frame.image.height}") | |
encoder.addFrame( | |
frame.image, | |
frame.delay, | |
frame.xPosition, | |
frame.yPosition | |
) | |
val outputStream = outputStream | |
if (outputStream is FileOutputStream) { | |
val currentSize = outputStream.channel.size() | |
println("Frame $index size is ${(currentSize - previousSize).toDouble() / 1000}Kb; Total size is: $currentSize") | |
previousSize = currentSize | |
} | |
} | |
encoder.finish() | |
} | |
} | |
data class GIFFrame(val image: BufferedImage, var delay: Int, val dropPixelSimilarDistanceOverride: Int? = null) | |
data class IntermediaryGIFFrame( | |
val image: BufferedImage, | |
var delay: Int, | |
val xPosition: Int, | |
val yPosition: Int | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.perfectdreams.discordbanner.gifs | |
import java.io.IOException | |
import java.io.OutputStream | |
// ============================================================================== | |
// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. | |
// K Weiner 12/00 | |
internal class LZWEncoder( | |
private val imgW: Int, | |
private val imgH: Int, | |
private val pixAry: ByteArray, | |
color_depth: Int | |
) { | |
private val initCodeSize: Int | |
private var remaining = 0 | |
private var curPixel = 0 | |
// GIF Image compression - modified 'compress' | |
// | |
// Based on: compress.c - File compression ala IEEE Computer, June 1984. | |
// | |
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) | |
// Jim McKie (decvax!mcvax!jim) | |
// Steve Davies (decvax!vax135!petsd!peora!srd) | |
// Ken Turkowski (decvax!decwrl!turtlevax!ken) | |
// James A. Woods (decvax!ihnp4!ames!jaw) | |
// Joe Orost (decvax!vax135!petsd!joe) | |
var n_bits // number of bits/code | |
= 0 | |
var maxbits = BITS // user settable max # bits/code | |
var maxcode // maximum code, given n_bits | |
= 0 | |
var maxmaxcode = 1 shl BITS // should NEVER generate this code | |
var htab = IntArray(HSIZE) | |
var codetab = IntArray(HSIZE) | |
var hsize = HSIZE // for dynamic table sizing | |
var free_ent = 0 // first unused entry | |
// block compression parameters -- after all codes are used up, | |
// and compression rate changes, start over. | |
var clear_flg = false | |
// Algorithm: use open addressing double hashing (no chaining) on the | |
// prefix code / next character combination. We do a variant of Knuth's | |
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime | |
// secondary probe. Here, the modular division first probe is gives way | |
// to a faster exclusive-or manipulation. Also do block compression with | |
// an adaptive reset, whereby the code table is cleared when the compression | |
// ratio decreases, but after the table fills. The variable-length output | |
// codes are re-sized at this point, and a special CLEAR code is generated | |
// for the decompressor. Late addition: construct the table according to | |
// file size for noticeable speed improvement on small files. Please direct | |
// questions about this implementation to ames!jaw. | |
var g_init_bits = 0 | |
var ClearCode = 0 | |
var EOFCode = 0 | |
// output | |
// | |
// Output the given code. | |
// Inputs: | |
// code: A n_bits-bit integer. If == -1, then EOF. This assumes | |
// that n_bits =< wordsize - 1. | |
// Outputs: | |
// Outputs code to the file. | |
// Assumptions: | |
// Chars are 8 bits long. | |
// Algorithm: | |
// Maintain a BITS character long buffer (so that 8 codes will | |
// fit in it exactly). Use the VAX insv instruction to insert each | |
// code in turn. When the buffer fills up empty it and start over. | |
var cur_accum = 0 | |
var cur_bits = 0 | |
var masks = intArrayOf( | |
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, | |
0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF | |
) | |
// Number of characters so far in this 'packet' | |
var a_count = 0 | |
// Define the storage for the packet accumulator | |
var accum = ByteArray(256) | |
// Add a character to the end of the current packet, and if it is 254 | |
// characters, flush the packet to disk. | |
@Throws(IOException::class) | |
fun char_out(c: Byte, outs: OutputStream) { | |
accum[a_count++] = c | |
if (a_count >= 254) flush_char(outs) | |
} | |
// Clear out the hash table | |
// table clear for block compress | |
@Throws(IOException::class) | |
fun cl_block(outs: OutputStream) { | |
cl_hash(hsize) | |
free_ent = ClearCode + 2 | |
clear_flg = true | |
output(ClearCode, outs) | |
} | |
// reset code table | |
fun cl_hash(hsize: Int) { | |
for (i in 0 until hsize) htab[i] = -1 | |
} | |
@Throws(IOException::class) | |
fun compress(init_bits: Int, outs: OutputStream) { | |
var fcode: Int | |
var i /* = 0 */: Int | |
var c: Int | |
var ent: Int | |
var disp: Int | |
val hsize_reg: Int | |
var hshift: Int | |
// Set up the globals: g_init_bits - initial number of bits | |
g_init_bits = init_bits | |
// Set up the necessary values | |
clear_flg = false | |
n_bits = g_init_bits | |
maxcode = MAXCODE(n_bits) | |
ClearCode = 1 shl init_bits - 1 | |
EOFCode = ClearCode + 1 | |
free_ent = ClearCode + 2 | |
a_count = 0 // clear packet | |
ent = nextPixel() | |
hshift = 0 | |
fcode = hsize | |
while (fcode < 65536) { | |
++hshift | |
fcode *= 2 | |
} | |
hshift = 8 - hshift // set hash code range bound | |
hsize_reg = hsize | |
cl_hash(hsize_reg) // clear hash table | |
output(ClearCode, outs) | |
outer_loop@ while (nextPixel().also { c = it } != EOF) { | |
fcode = (c shl maxbits) + ent | |
i = c shl hshift xor ent // xor hashing | |
if (htab[i] == fcode) { | |
ent = codetab[i] | |
continue | |
} else if (htab[i] >= 0) // non-empty slot | |
{ | |
disp = hsize_reg - i // secondary hash (after G. Knott) | |
if (i == 0) disp = 1 | |
do { | |
if (disp.let { i -= it; i } < 0) i += hsize_reg | |
if (htab[i] == fcode) { | |
ent = codetab[i] | |
continue@outer_loop | |
} | |
} while (htab[i] >= 0) | |
} | |
output(ent, outs) | |
ent = c | |
if (free_ent < maxmaxcode) { | |
codetab[i] = free_ent++ // code -> hashtable | |
htab[i] = fcode | |
} else cl_block(outs) | |
} | |
// Put out the final code. | |
output(ent, outs) | |
output(EOFCode, outs) | |
} | |
// ---------------------------------------------------------------------------- | |
@Throws(IOException::class) | |
fun encode(os: OutputStream) { | |
os.write(initCodeSize) // write "initial code size" byte | |
remaining = imgW * imgH // reset navigation variables | |
curPixel = 0 | |
compress(initCodeSize + 1, os) // compress and write the pixel data | |
os.write(0) // write block terminator | |
} | |
// Flush the packet to disk, and reset the accumulator | |
@Throws(IOException::class) | |
fun flush_char(outs: OutputStream) { | |
if (a_count > 0) { | |
outs.write(a_count) | |
outs.write(accum, 0, a_count) | |
a_count = 0 | |
} | |
} | |
fun MAXCODE(n_bits: Int): Int { | |
return (1 shl n_bits) - 1 | |
} | |
// ---------------------------------------------------------------------------- | |
// Return the next pixel from the image | |
// ---------------------------------------------------------------------------- | |
private fun nextPixel(): Int { | |
if (remaining == 0) return EOF | |
--remaining | |
val pix = pixAry[curPixel++] | |
return pix.toInt() and 0xff | |
} | |
@Throws(IOException::class) | |
fun output(code: Int, outs: OutputStream) { | |
cur_accum = cur_accum and masks[cur_bits] | |
cur_accum = if (cur_bits > 0) cur_accum or (code shl cur_bits) else code | |
cur_bits += n_bits | |
while (cur_bits >= 8) { | |
char_out((cur_accum and 0xff).toByte(), outs) | |
cur_accum = cur_accum shr 8 | |
cur_bits -= 8 | |
} | |
// If the next entry is going to be too big for the code size, | |
// then increase it, if possible. | |
if (free_ent > maxcode || clear_flg) { | |
if (clear_flg) { | |
maxcode = MAXCODE(g_init_bits.also { n_bits = it }) | |
clear_flg = false | |
} else { | |
++n_bits | |
maxcode = if (n_bits == maxbits) maxmaxcode else MAXCODE(n_bits) | |
} | |
} | |
if (code == EOFCode) { | |
// At EOF, write the rest of the buffer. | |
while (cur_bits > 0) { | |
char_out((cur_accum and 0xff).toByte(), outs) | |
cur_accum = cur_accum shr 8 | |
cur_bits -= 8 | |
} | |
flush_char(outs) | |
} | |
} | |
companion object { | |
private const val EOF = -1 | |
// GIFCOMPR.C - GIF Image compression routines | |
// | |
// Lempel-Ziv compression based on 'compress'. GIF modifications by | |
// David Rowley (mgardi@watdcsu.waterloo.edu) | |
// General DEFINEs | |
const val BITS = 12 | |
const val HSIZE = 5003 // 80% occupancy | |
} | |
// ---------------------------------------------------------------------------- | |
init { | |
initCodeSize = Math.max(2, color_depth) | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.perfectdreams.discordbanner.gifs | |
import java.lang.Exception | |
/* | |
* NeuQuant Neural-Net Quantization Algorithm | |
* ------------------------------------------ | |
* | |
* Copyright (c) 1994 Anthony Dekker | |
* | |
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See | |
* "Kohonen neural networks for optimal colour quantization" in "Network: | |
* Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of | |
* the algorithm. | |
* | |
* Any party obtaining a copy of these files from the author, directly or | |
* indirectly, is granted, free of charge, a full and unrestricted irrevocable, | |
* world-wide, paid up, royalty-free, nonexclusive right and license to deal in | |
* this software and documentation files (the "Software"), including without | |
* limitation the rights to use, copy, modify, merge, publish, distribute, | |
* sublicense, and/or sell copies of the Software, and to permit persons who | |
* receive copies from any such party to do so, with the only requirement being | |
* that this copyright notice remain intact. | |
*/ | |
/* | |
* NeuQuant Neural-Net Quantization Algorithm | |
* ------------------------------------------ | |
* | |
* Copyright (c) 1994 Anthony Dekker | |
* | |
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See | |
* "Kohonen neural networks for optimal colour quantization" in "Network: | |
* Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of | |
* the algorithm. | |
* | |
* Any party obtaining a copy of these files from the author, directly or | |
* indirectly, is granted, free of charge, a full and unrestricted irrevocable, | |
* world-wide, paid up, royalty-free, nonexclusive right and license to deal in | |
* this software and documentation files (the "Software"), including without | |
* limitation the rights to use, copy, modify, merge, publish, distribute, | |
* sublicense, and/or sell copies of the Software, and to permit persons who | |
* receive copies from any such party to do so, with the only requirement being | |
* that this copyright notice remain intact. | |
*/ | |
// Ported to Java 12/00 K Weiner | |
internal class NeuQuant(thepic: ByteArray, len: Int, sample: Int) { | |
protected var alphadec /* biased by 10 bits */ = 0 | |
/* | |
* Types and Global Variables -------------------------- | |
*/ | |
protected var thepicture /* the input image itself */: ByteArray | |
protected var lengthcount /* lengthcount = H*W*3 */: Int | |
protected var samplefac /* sampling factor 1..30 */: Int | |
// typedef int pixel[4]; /* BGRc */ | |
protected var network /* the network itself - [netsize][4] */: Array<IntArray?> | |
protected var netindex = IntArray(256) | |
/* for network lookup - really 256 */ | |
protected var bias = IntArray(netsize) | |
/* bias and freq arrays for learning */ | |
protected var freq = IntArray(netsize) | |
protected var radpower = IntArray(initrad) | |
fun colorMap(): ByteArray { | |
val map = ByteArray(3 * netsize) | |
val index = IntArray(netsize) | |
for (i in 0 until netsize) index[network[i]!![3]] = i | |
var k = 0 | |
for (i in 0 until netsize) { | |
val j = index[i] | |
map[k++] = network[j]!![0].toByte() | |
map[k++] = network[j]!![1].toByte() | |
map[k++] = network[j]!![2].toByte() | |
} | |
return map | |
} | |
/* | |
* Insertion sort of network and building of netindex[0..255] (to do after | |
* unbias) | |
* ------------------------------------------------------------------------------- | |
*/ | |
fun inxbuild() { | |
var i: Int | |
var j: Int | |
var smallpos: Int | |
var smallval: Int | |
var p: IntArray? | |
var q: IntArray? | |
var previouscol: Int | |
var startpos: Int | |
previouscol = 0 | |
startpos = 0 | |
i = 0 | |
while (i < netsize) { | |
p = network[i] | |
smallpos = i | |
smallval = p!![1] /* index on g */ | |
/* find smallest in i..netsize-1 */j = i + 1 | |
while (j < netsize) { | |
q = network[j] | |
if (q!![1] < smallval) { /* index on g */ | |
smallpos = j | |
smallval = q[1] /* index on g */ | |
} | |
j++ | |
} | |
q = network[smallpos] | |
/* swap p (i) and q (smallpos) entries */if (i != smallpos) { | |
j = q!![0] | |
q[0] = p[0] | |
p[0] = j | |
j = q[1] | |
q[1] = p[1] | |
p[1] = j | |
j = q[2] | |
q[2] = p[2] | |
p[2] = j | |
j = q[3] | |
q[3] = p[3] | |
p[3] = j | |
} | |
/* smallval entry is now in position i */if (smallval != previouscol) { | |
netindex[previouscol] = startpos + i shr 1 | |
j = previouscol + 1 | |
while (j < smallval) { | |
netindex[j] = i | |
j++ | |
} | |
previouscol = smallval | |
startpos = i | |
} | |
i++ | |
} | |
netindex[previouscol] = startpos + maxnetpos shr 1 | |
j = previouscol + 1 | |
while (j < 256) { | |
netindex[j] = maxnetpos /* really 256 */ | |
j++ | |
} | |
} | |
/* | |
* Main Learning Loop ------------------ | |
*/ | |
fun learn() { | |
var i: Int | |
var j: Int | |
var b: Int | |
var g: Int | |
var r: Int | |
var radius: Int | |
var rad: Int | |
var alpha: Int | |
val step: Int | |
var delta: Int | |
val samplepixels: Int | |
val p: ByteArray | |
var pix: Int | |
val lim: Int | |
if (lengthcount < minpicturebytes) samplefac = 1 | |
alphadec = 30 + (samplefac - 1) / 3 | |
p = thepicture | |
pix = 0 | |
lim = lengthcount | |
samplepixels = lengthcount / (3 * samplefac) | |
delta = samplepixels / ncycles | |
alpha = initalpha | |
radius = initradius | |
rad = radius shr radiusbiasshift | |
if (rad <= 1) rad = 0 | |
i = 0 | |
while (i < rad) { | |
radpower[i] = alpha * ((rad * rad - i * i) * radbias / (rad * rad)) | |
i++ | |
} | |
// fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad); | |
step = | |
if (lengthcount < minpicturebytes) 3 else if (lengthcount % prime1 != 0) 3 * prime1 else { | |
if (lengthcount % prime2 != 0) 3 * prime2 else { | |
if (lengthcount % prime3 != 0) 3 * prime3 else 3 * prime4 | |
} | |
} | |
i = 0 | |
while (i < samplepixels) { | |
b = p[pix + 0].toInt() and 0xff shl netbiasshift | |
g = p[pix + 1].toInt() and 0xff shl netbiasshift | |
r = p[pix + 2].toInt() and 0xff shl netbiasshift | |
j = contest(b, g, r) | |
altersingle(alpha, j, b, g, r) | |
if (rad != 0) alterneigh(rad, j, b, g, r) /* alter neighbours */ | |
pix += step | |
if (pix >= lim) pix -= lengthcount | |
i++ | |
if (delta == 0) delta = 1 | |
if (i % delta == 0) { | |
alpha -= alpha / alphadec | |
radius -= radius / radiusdec | |
rad = radius shr radiusbiasshift | |
if (rad <= 1) rad = 0 | |
j = 0 | |
while (j < rad) { | |
radpower[j] = alpha * ((rad * rad - j * j) * radbias / (rad * rad)) | |
j++ | |
} | |
} | |
} | |
// fprintf(stderr,"finished 1D learning: final alpha=%f | |
// !\n",((float)alpha)/initalpha); | |
} | |
/* | |
* Search for BGR values 0..255 (after net is unbiased) and return colour | |
* index | |
* ---------------------------------------------------------------------------- | |
*/ | |
fun map(b: Int, g: Int, r: Int): Int { | |
var i: Int | |
var j: Int | |
var dist: Int | |
var a: Int | |
var bestd: Int | |
var p: IntArray? | |
var best: Int | |
bestd = 1000 /* biggest possible dist is 256*3 */ | |
best = -1 | |
i = netindex[g] /* index on g */ | |
j = i - 1 /* start at netindex[g] and work outwards */ | |
while (i < netsize || j >= 0) { | |
if (i < netsize) { | |
p = network[i] | |
dist = p!![1] - g /* inx key */ | |
if (dist >= bestd) i = netsize /* stop iter */ else { | |
i++ | |
if (dist < 0) dist = -dist | |
a = p[0] - b | |
if (a < 0) a = -a | |
dist += a | |
if (dist < bestd) { | |
a = p[2] - r | |
if (a < 0) a = -a | |
dist += a | |
if (dist < bestd) { | |
bestd = dist | |
best = p[3] | |
} | |
} | |
} | |
} | |
if (j >= 0) { | |
p = network[j] | |
dist = g - p!![1] /* inx key - reverse dif */ | |
if (dist >= bestd) j = -1 /* stop iter */ else { | |
j-- | |
if (dist < 0) dist = -dist | |
a = p[0] - b | |
if (a < 0) a = -a | |
dist += a | |
if (dist < bestd) { | |
a = p[2] - r | |
if (a < 0) a = -a | |
dist += a | |
if (dist < bestd) { | |
bestd = dist | |
best = p[3] | |
} | |
} | |
} | |
} | |
} | |
return best | |
} | |
fun process(): ByteArray { | |
learn() | |
unbiasnet() | |
inxbuild() | |
return colorMap() | |
} | |
/* | |
* Unbias network to give byte values 0..255 and record position i to prepare | |
* for sort | |
* ----------------------------------------------------------------------------------- | |
*/ | |
fun unbiasnet() { | |
var i: Int | |
var j: Int | |
i = 0 | |
while (i < netsize) { | |
network[i]!![0] = network[i]!![0] shr netbiasshift | |
network[i]!![1] = network[i]!![1] shr netbiasshift | |
network[i]!![2] = network[i]!![2] shr netbiasshift | |
network[i]!![3] = i /* record colour no */ | |
i++ | |
} | |
} | |
/* | |
* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in | |
* radpower[|i-j|] | |
* --------------------------------------------------------------------------------- | |
*/ | |
protected fun alterneigh(rad: Int, i: Int, b: Int, g: Int, r: Int) { | |
var j: Int | |
var k: Int | |
var lo: Int | |
var hi: Int | |
var a: Int | |
var m: Int | |
var p: IntArray? | |
lo = i - rad | |
if (lo < -1) lo = -1 | |
hi = i + rad | |
if (hi > netsize) hi = netsize | |
j = i + 1 | |
k = i - 1 | |
m = 1 | |
while (j < hi || k > lo) { | |
a = radpower[m++] | |
if (j < hi) { | |
p = network[j++] | |
try { | |
p!![0] -= a * (p!![0] - b) / alpharadbias | |
p[1] -= a * (p[1] - g) / alpharadbias | |
p[2] -= a * (p[2] - r) / alpharadbias | |
} catch (e: Exception) { | |
} // prevents 1.3 miscompilation | |
} | |
if (k > lo) { | |
p = network[k--] | |
try { | |
p!![0] -= a * (p!![0] - b) / alpharadbias | |
p[1] -= a * (p[1] - g) / alpharadbias | |
p[2] -= a * (p[2] - r) / alpharadbias | |
} catch (e: Exception) { | |
} | |
} | |
} | |
} | |
/* | |
* Move neuron i towards biased (b,g,r) by factor alpha | |
* ---------------------------------------------------- | |
*/ | |
protected fun altersingle(alpha: Int, i: Int, b: Int, g: Int, r: Int) { | |
/* alter hit neuron */ | |
val n = network[i] | |
n!![0] -= alpha * (n!![0] - b) / initalpha | |
n[1] -= alpha * (n[1] - g) / initalpha | |
n[2] -= alpha * (n[2] - r) / initalpha | |
} | |
/* | |
* Search for biased BGR values ---------------------------- | |
*/ | |
protected fun contest(b: Int, g: Int, r: Int): Int { | |
/* finds closest neuron (min dist) and updates freq */ | |
/* finds best neuron (min dist-bias) and returns position */ | |
/* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ | |
/* bias[i] = gamma*((1/netsize)-freq[i]) */ | |
var i: Int | |
var dist: Int | |
var a: Int | |
var biasdist: Int | |
var betafreq: Int | |
var bestpos: Int | |
var bestbiaspos: Int | |
var bestd: Int | |
var bestbiasd: Int | |
var n: IntArray? | |
bestd = (1 shl 31).inv() | |
bestbiasd = bestd | |
bestpos = -1 | |
bestbiaspos = bestpos | |
i = 0 | |
while (i < netsize) { | |
n = network[i] | |
dist = n!![0] - b | |
if (dist < 0) dist = -dist | |
a = n[1] - g | |
if (a < 0) a = -a | |
dist += a | |
a = n[2] - r | |
if (a < 0) a = -a | |
dist += a | |
if (dist < bestd) { | |
bestd = dist | |
bestpos = i | |
} | |
biasdist = dist - (bias[i] shr intbiasshift - netbiasshift) | |
if (biasdist < bestbiasd) { | |
bestbiasd = biasdist | |
bestbiaspos = i | |
} | |
betafreq = freq[i] shr betashift | |
freq[i] -= betafreq | |
bias[i] += betafreq shl gammashift | |
i++ | |
} | |
freq[bestpos] += beta | |
bias[bestpos] -= betagamma | |
return bestbiaspos | |
} | |
companion object { | |
protected const val netsize = 256 /* number of colours used */ | |
/* four primes near 500 - assume no image has a length so large */ /* that it is divisible by all four primes */ | |
protected const val prime1 = 499 | |
protected const val prime2 = 491 | |
protected const val prime3 = 487 | |
protected const val prime4 = 503 | |
protected const val minpicturebytes = 3 * prime4 | |
/* minimum size for input image */ /* | |
* Program Skeleton ---------------- [select samplefac in range 1..30] [read | |
* image from input file] pic = (unsigned char*) malloc(3*width*height); | |
* initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output | |
* image header, using writecolourmap(f)] inxbuild(); write output image using | |
* inxsearch(b,g,r) | |
*/ | |
/* | |
* Network Definitions ------------------- | |
*/ | |
protected const val maxnetpos = netsize - 1 | |
protected const val netbiasshift = 4 /* bias for colour values */ | |
protected const val ncycles = 100 /* no. of learning cycles */ | |
/* defs for freq and bias */ | |
protected const val intbiasshift = 16 /* bias for fractions */ | |
protected const val intbias = 1 shl intbiasshift | |
protected const val gammashift = 10 /* gamma = 1024 */ | |
protected const val gamma = 1 shl gammashift | |
protected const val betashift = 10 | |
protected const val beta = intbias shr betashift /* beta = 1/1024 */ | |
protected const val betagamma = intbias shl gammashift - betashift | |
/* defs for decreasing radius factor */ | |
protected const val initrad = netsize shr 3 /* | |
* for 256 cols, radius | |
* starts | |
*/ | |
protected const val radiusbiasshift = 6 /* at 32.0 biased by 6 bits */ | |
protected const val radiusbias = 1 shl radiusbiasshift | |
protected const val initradius = initrad * radiusbias /* | |
* and | |
* decreases | |
* by a | |
*/ | |
protected const val radiusdec = 30 /* factor of 1/30 each cycle */ | |
/* defs for decreasing alpha factor */ | |
protected const val alphabiasshift = 10 /* alpha starts at 1.0 */ | |
protected const val initalpha = 1 shl alphabiasshift | |
/* radbias and alpharadbias used for radpower calculation */ | |
protected const val radbiasshift = 8 | |
protected const val radbias = 1 shl radbiasshift | |
protected const val alpharadbshift = alphabiasshift + radbiasshift | |
protected const val alpharadbias = 1 shl alpharadbshift | |
} | |
/* radpower for precomputation */ /* | |
* Initialise network in range (0,0,0) to (255,255,255) and set parameters | |
* ----------------------------------------------------------------------- | |
*/ | |
init { | |
var i: Int | |
var p: IntArray? | |
thepicture = thepic | |
lengthcount = len | |
samplefac = sample | |
network = arrayOfNulls(netsize) | |
i = 0 | |
while (i < netsize) { | |
network[i] = IntArray(4) | |
p = network[i] | |
p!![2] = (i shl netbiasshift + 8) / netsize | |
p!![1] = p!![2] | |
p!![0] = p!![1] | |
freq[i] = intbias / netsize /* 1/netsize */ | |
bias[i] = 0 | |
i++ | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment