Skip to content

Commit

Permalink
#131: introduces LPixelBuffer, simplifies block paint code
Browse files Browse the repository at this point in the history
  • Loading branch information
rladstaetter committed Oct 22, 2023
1 parent 92e2ecf commit 46ff036
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 246 deletions.
8 changes: 4 additions & 4 deletions app/src/main/scala/app/logorrr/model/LogEntryFileReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ import java.util
object LogEntryFileReader extends CanLog {

private def mkLogEntryList(parseEntryForTimeInstant: String => Option[Instant])
(logFilePath: Path): ObservableList[LogEntry] = timeR({
(logFilePath: Path): ObservableList[LogEntry] = {
var lineNumber: Int = 0
val arraylist = new util.ArrayList[LogEntry]()
LogFileReader.readFromFile(logFilePath).map(l => {
lineNumber = lineNumber + 1
arraylist.add(LogEntry(lineNumber, l, parseEntryForTimeInstant(l)))
})
FXCollections.observableList(arraylist)
}, s"Imported ${logFilePath.toAbsolutePath.toString} ... ")
}

def from(logFilePath: Path, filters: Seq[Fltr], logEntryTimeFormat: LogEntryInstantFormat): ObservableList[LogEntry] = {
def from(logFilePath: Path, logEntryTimeFormat: LogEntryInstantFormat): ObservableList[LogEntry] = {
mkLogEntryList(l => LogEntryInstantFormat.parseInstant(l, logEntryTimeFormat))(logFilePath)
}

def from(logFile: Path, filters: Seq[Fltr]): ObservableList[LogEntry] = mkLogEntryList(_ => None)(logFile)
def from(logFile: Path): ObservableList[LogEntry] = mkLogEntryList(_ => None)(logFile)


}
Expand Down
9 changes: 2 additions & 7 deletions app/src/main/scala/app/logorrr/model/LogFileReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,10 @@ object LogFileReader extends CanLog {
OsxBridge.registerPath(logFile.toAbsolutePath.toString)
}
val lines = FileManager.fromPath(logFile)
logEmptyLogFile(logFile, lines)
lines
}

private def logEmptyLogFile(logFile: Path, lines: Seq[String]): Unit = {
if (lines.isEmpty) {
logWarn(s"${logFile.toAbsolutePath.toString} was empty.")
} else {
// logTrace(s"${logFile.toAbsolutePath.toString} has ${lines.size} lines.")
}
lines
}

}
7 changes: 3 additions & 4 deletions app/src/main/scala/app/logorrr/model/LogFileSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,13 @@ case class LogFileSettings(pathAsString: String
def readEntries(): ObservableList[LogEntry] = {
if (isPathValid) {
Try(someLogEntryInstantFormat match {
case Some(instantFormat) => LogEntryFileReader.from(path, filters, instantFormat)
case None => LogEntryFileReader.from(path, filters)
case Some(instantFormat) => LogEntryFileReader.from(path, instantFormat)
case None => LogEntryFileReader.from(path)
}) match {
case Success(logEntries) =>
// logTrace(s"Opened $pathAsString ... ")
logEntries
case Failure(ex) =>
val msg = s"Could not import file $pathAsString"
val msg = s"Could not load file $pathAsString"
logException(msg, ex)
FXCollections.observableArrayList()
}
Expand Down
216 changes: 53 additions & 163 deletions app/src/main/scala/app/logorrr/views/block/BlockImage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import app.logorrr.model.LogEntry
import app.logorrr.util.{CanLog, ColorUtil, JfxUtils}
import app.logorrr.views.search.Filter
import javafx.beans.property.{SimpleIntegerProperty, SimpleListProperty, SimpleObjectProperty}
import javafx.beans.value.ChangeListener
import javafx.beans.{InvalidationListener, Observable}
import javafx.collections.FXCollections
import javafx.geometry.Rectangle2D
import javafx.scene.image.{PixelBuffer, PixelFormat, WritableImage}
import javafx.scene.image.WritableImage
import javafx.scene.paint.Color

import java.nio.IntBuffer
import scala.jdk.CollectionConverters.ListHasAsScala
import scala.jdk.CollectionConverters.CollectionHasAsScala

object BlockImage {

Expand All @@ -23,189 +21,81 @@ object BlockImage {

}

class BlockImage extends CanLog {

val filtersProperty = new SimpleListProperty[Filter]()
class BlockImage(name: String
, widthProperty: SimpleIntegerProperty
, blockSizeProperty: SimpleIntegerProperty
, entriesProperty: SimpleListProperty[LogEntry]
, filtersProperty: SimpleListProperty[Filter]
, selectedElemProperty: SimpleObjectProperty[LogEntry]) extends CanLog {

var pixelBuffer: PixelBuffer[IntBuffer] = _
var intBuffer: IntBuffer = _
var rawInts: Array[Int] = _
var background: Array[Int] = _
var roi: Rectangle2D = _
var lpb: LPixelBuffer = _

private val redrawListener: InvalidationListener = (_: Observable) => repaint()
/**
* height property is calculated on the fly depending on the blockwidth/blockheight,
* width of BlockImage, number of elements and max size of possible of texture (4096).
* */
val heightProperty: SimpleIntegerProperty = new SimpleIntegerProperty()

val entries = new SimpleListProperty[LogEntry](FXCollections.observableArrayList())
val imageProperty = new SimpleObjectProperty[WritableImage]()

/* if blockwidth is changed redraw */
val blockWidthProperty: SimpleIntegerProperty = {
val p = new SimpleIntegerProperty()
p.addListener(redrawListener)
p
}
private val heightListener: ChangeListener[Number] = JfxUtils.onNew[Number](height => {
// logTrace("heightListener " + height)
resetBackingImage(getWidth, height.intValue)
})

/* if blockheight is changed redraw */
val blockHeightProperty: SimpleIntegerProperty = {
val p = new SimpleIntegerProperty()
p.addListener(redrawListener)
p
private val blockSizeListener: InvalidationListener = (_: Observable) => {
repaint("blockSizeListener")
}

private def getBlockWidth(): Int = blockWidthProperty.get()
val widthListener: ChangeListener[Number] = JfxUtils.onNew[Number](_ => {
repaint("widthListener")
})

def shutdown() : Unit = {
clearBackingPixelBuffer()
init()

def init(): Unit = {
addListener()
}


def shutdown(): Unit = {
lpb = null
// just wipe out everything (?!)
Option(pixelBuffer).foreach(_.getBuffer.clear())
Option(intBuffer).foreach(_.clear())
this.rawInts = null
this.background = null
this.intBuffer = null
this.pixelBuffer = null
this.roi = null
imageProperty.set(null)
removeListener()
}

def removeListener(): Unit = {
blockWidthProperty.removeListener(redrawListener)
blockHeightProperty.removeListener(redrawListener)
}

private def getBlockHeight(): Int = blockHeightProperty.get()

val imageProperty = new SimpleObjectProperty[WritableImage]()

/**
* height property is calculated on the fly depending on the blockwidth/blockheight,
* width of SQImage, number of elements and max size of possible of texture (4096).
* */
val heightProperty: SimpleIntegerProperty = {
val p = new SimpleIntegerProperty()
p.addListener(JfxUtils.onNew[Number](height => resetBackingImage(getWidth(), height.intValue)))
p
}
val widthProperty: SimpleIntegerProperty = {
val p = new SimpleIntegerProperty()
p.addListener(JfxUtils.onNew[Number](_ => repaint()))
p
def addListener(): Unit = {
heightProperty.addListener(heightListener)
widthProperty.addListener(widthListener)
blockSizeProperty.addListener(blockSizeListener)
}

def setHeight(height: Int): Unit = heightProperty.set(height)

def getHeight(): Int = heightProperty.get()

def getWidth(): Int = widthProperty.get()

private def resetBackingImage(width: Int, height: Int): Unit = {
clearBackingPixelBuffer()

assert(width != 0, s"width was $width.")
assert(height != 0, s"height was $height.")
assert(width <= BlockImage.MaxWidth, s"width was $width which exceeds ${BlockImage.MaxWidth}.")
assert(height <= BlockImage.MaxHeight, s"height was $height which exceeds ${BlockImage.MaxHeight}.")
assert(height * width > 0)

val bgColor = Color.WHITE
val rawInts = Array.fill(width * height)(ColorUtil.toARGB(Color.WHITE))
val buffer: IntBuffer = IntBuffer.wrap(rawInts)
val pixelBuffer = new PixelBuffer[IntBuffer](width, height, buffer, PixelFormat.getIntArgbPreInstance)
val backingImage = new WritableImage(pixelBuffer)
this.intBuffer = buffer
this.rawInts = buffer.array()
this.background = Array.fill(width * height)(ColorUtil.toARGB(bgColor))
this.pixelBuffer = pixelBuffer
this.roi = new Rectangle2D(0, 0, width, height)
this.imageProperty.set(backingImage)
def removeListener(): Unit = {
heightProperty.removeListener(heightListener)
widthProperty.removeListener(widthListener)
blockSizeProperty.removeListener(blockSizeListener)
}

def clearBackingPixelBuffer(): Unit = {
Option(this.intBuffer) match {
case Some(value) =>
value.clear()

this.rawInts = null
case None =>
}
private def resetBackingImage(width: Int, height: Int): Unit = {
lpb = LPixelBuffer(width
, height
, blockSizeProperty
, entriesProperty
, filtersProperty
, selectedElemProperty
, Array.fill(width * height)(ColorUtil.toARGB(Color.WHITE)))
this.imageProperty.set(new WritableImage(lpb))
}

def cleanBackground(): Unit = System.arraycopy(background, 0, rawInts, 0, background.length)

// todo check visibility
def repaint(): Unit = {
Option(pixelBuffer) match {
case Some(pb) =>
if (getBlockWidth() != 0) {
pb.updateBuffer((_: PixelBuffer[IntBuffer]) => {
cleanBackground()
var i = 0
entries.forEach(e => {
drawRect(i, Filter.calcColor(e.value, filtersProperty.asScala.toSeq))
i = i + 1
})
roi
})
} else {
logWarn(s"getBlockWidth() = ${getBlockWidth()}")
}
case None => // logTrace("pixelBuffer was null")
}
}
def repaint(ctx: String): Unit = lpb.repaint(ctx, filtersProperty.asScala.toSeq, selectedElemProperty.get())

/**
* draws a filled rectangle on the given index
*
* @param e
*/
def draw(index: Int, color: Color): Unit = {
Option(pixelBuffer) foreach {
pb =>
if (getBlockWidth() != 0) {
repaint()
pb.updateBuffer((_: PixelBuffer[IntBuffer]) => {
// drawRect(e.lineNumber - 1, Filter.calcColor(e.value, filtersProperty.asScala.toSeq).darker().darker())
drawRect(index, color)
roi
})
}
}
}

def drawRect(i: Int, color: Color): Unit = {
val width = getWidth()
val nrOfBlocksInX = width / getBlockWidth()
val xPos = (i % nrOfBlocksInX) * getBlockWidth()
val yPos = (i / nrOfBlocksInX) * getBlockHeight()
drawRect(
color
, xPos
, yPos
, getBlockWidth()
, getBlockHeight()
, getWidth()
)
}

def drawRect(color: Color
, x: Int
, y: Int
, width: Int
, height: Int
, canvasWidth: Int): Unit = {

val col = ColorUtil.toARGB(color)
val maxHeight = y + height
val lineArray = Array.fill(width - 1)(col)
for (ly <- y until maxHeight - 1) {
setPixelsAt(ly * canvasWidth + x, lineArray)
}
}
def setHeight(height: Int): Unit = heightProperty.set(height)

def setPixelsAt(destPos: Int, newPixels: Array[Int]): Unit = {
if (destPos + newPixels.length < rawInts.length) { // TODO check should not be necessary??
System.arraycopy(newPixels, 0, rawInts, destPos, newPixels.length)
}
}
def getWidth: Int = widthProperty.get()

}
Loading

0 comments on commit 46ff036

Please sign in to comment.