Docker image URL has been switched from git.corp.winpromise.com:5050 to registry.corp.winpromise.com Remember to update your .gitlab-ci.yml to reflect this change.

Skip to content
Commits on Source (5)
......@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.3.0](https://git.corp.winpromise.com/jacob.lee/disk-extractor/compare/0.2.0...0.3.0) (2024-09-17)
### Features
* drawline method change to use lib draw2d ([363b1cb](https://git.corp.winpromise.com/jacob.lee/disk-extractor/commit/363b1cb002a63f8acfa4440cf91c01fbdf9d7be9))
* **rlc5:** add drawline function to rlc5 extraction ([b93aa1e](https://git.corp.winpromise.com/jacob.lee/disk-extractor/commit/b93aa1e3816da52082b9ea33586e9c413e252bc9))
## [0.2.0](https://git.corp.winpromise.com/jacob.lee/disk-extractor/compare/v0.1.10...0.2.0) (2024-08-28)
......
......@@ -5,7 +5,10 @@ go 1.22.1
require github.com/fatih/color v1.16.0
require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/llgcode/draw2d v0.0.0-20240627062922-0ed1ff131195 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
)
......@@ -18,12 +18,13 @@ var (
)
func main() {
cyan.Println("Running Disk Extractor v0.10.0")
cyan.Println("Running Disk Extractor")
input := flag.String("i", "", "path to input file (can be case or directory of cases)")
privateKey := flag.String("p", "", "path to private key")
outDir := flag.String("o", rlc5.DefaultOutDir, "path to output directory")
maxGoRoutines := flag.Int("m", rlc5.DefaultMaxGoRoutines, "max number of go routines")
comfirm := flag.Bool("y", false, "skip confirmation")
triggerArea := flag.Bool("t", false, "draw trigger area")
flag.Parse()
if *input == "" {
......@@ -46,6 +47,7 @@ func main() {
if !*comfirm {
yellow.Printf("Setting Output directory: %s\n", *outDir)
yellow.Printf("Setting Max Threads: %d\n", *maxGoRoutines)
yellow.Printf("Setting Trigger Area: %t\n", *triggerArea)
yellow.Printf("About to extract %d cases, continue? (y/n): ", len(cases))
var c string
if _, err := fmt.Scan(&c); err != nil {
......@@ -61,7 +63,7 @@ func main() {
if err := rlc5.Extract(
cases,
*privateKey,
rlc5.WithOutDir(*outDir), rlc5.WithMaxGoRoutines(*maxGoRoutines),
rlc5.WithOutDir(*outDir), rlc5.WithMaxGoRoutines(*maxGoRoutines), rlc5.WithTriggerArea(*triggerArea),
); err != nil {
red.Printf("Failed to extract: %v\n", err)
return
......
......@@ -2,12 +2,15 @@ package rlc5
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"git.corp.winpromise.com/jacob.lee/disk-extractor/utils"
)
const (
......@@ -28,14 +31,16 @@ var (
)
type extractOptions struct {
outDir string
maxGoRoutines int
outDir string
maxGoRoutines int
drawTriggerArea bool
}
func newExtractOptions() *extractOptions {
return &extractOptions{
outDir: DefaultOutDir,
maxGoRoutines: DefaultMaxGoRoutines,
outDir: DefaultOutDir,
maxGoRoutines: DefaultMaxGoRoutines,
drawTriggerArea: false,
}
}
......@@ -61,6 +66,12 @@ func WithMaxGoRoutines(n int) extractOptionsFunc {
}
}
func WithTriggerArea(yes bool) extractOptionsFunc {
return func(o *extractOptions) {
o.drawTriggerArea = yes
}
}
func checkSupportedExt(name string) bool {
for _, ext := range SupportedExt {
if filepath.Ext(name) == ext {
......@@ -135,6 +146,56 @@ func Extract(cases map[string]io.Reader, privateKeyPath string, opts ...func(*ex
errsChan <- fmt.Errorf("failed to untar %s: %w", name, err)
return
}
if options.drawTriggerArea {
var (
wp WP
line1, line2 *utils.Line
)
for outName := range outputs {
if filepath.Ext(outName) != ".xml" {
continue
}
xml.Unmarshal(outputs[outName], &wp)
line1, err = wp.SystemInfo.EnforcementConfiguration.TriggerArea.Line1()
if err != nil {
fmt.Printf("File %s does not have trigger area\n", outName)
break
}
line2, err = wp.SystemInfo.EnforcementConfiguration.TriggerArea.Line2()
if err != nil {
fmt.Printf("File %s does not have trigger area\n", outName)
break
}
}
for outName := range outputs {
if filepath.Ext(outName) != ".png" && filepath.Ext(outName) != ".jpg" {
continue
}
if line1 == nil || line2 == nil {
continue
}
fmt.Printf("Draw trigger area for %s\n", outName)
img, err := utils.DrawLines(
bytes.NewReader(outputs[outName]),
*line1,
*line2,
utils.WithThickness(1),
)
if err != nil {
errsChan <- err
return
}
if err := utils.OutputImage(img, filepath.Join(
options.outDir,
fmt.Sprintf("%s_T.jpg", trimExt(outName)),
)); err != nil {
errsChan <- err
return
}
}
}
} else {
outputs[trimExt(filepath.Base(name))] = decrypted[:]
}
......
package rlc5
import (
"encoding/xml"
"strconv"
"git.corp.winpromise.com/jacob.lee/disk-extractor/utils"
)
type WP struct {
XMLName xml.Name `xml:"WP"`
Text string `xml:",chardata"`
DiskSummary struct {
Text string `xml:",chardata"`
PhotoCategorySummary struct {
Text string `xml:",chardata"`
PhotoCategory string `xml:"PhotoCategory"`
TotalDefectiveReasonType string `xml:"TotalDefectiveReasonType"`
TotalDefectiveReasonCode string `xml:"TotalDefectiveReasonCode"`
TotalDefectivePhotoNo string `xml:"TotalDefectivePhotoNo"`
PhotoType string `xml:"PhotoType"`
} `xml:"PhotoCategorySummary"`
} `xml:"DiskSummary"`
SystemInfo struct {
Text string `xml:",chardata"`
SystemSummary struct {
Text string `xml:",chardata"`
Device []struct {
Text string `xml:",chardata"`
Name string `xml:"Name,attr"`
Required string `xml:"Required,attr"`
Version string `xml:"Version,attr"`
Hardware struct {
Text string `xml:",chardata"`
Name string `xml:"Name,attr"`
ProductNumber string `xml:"ProductNumber,attr"`
SerialNumber string `xml:"serialNumber,attr"`
Software []struct {
Text string `xml:",chardata"`
Name string `xml:"Name,attr"`
Checksum string `xml:"Checksum,attr"`
Build string `xml:"Build,attr"`
Version string `xml:"Version,attr"`
} `xml:"Software"`
} `xml:"Hardware"`
} `xml:"Device"`
} `xml:"SystemSummary"`
EnforcementConfiguration struct {
Text string `xml:",chardata"`
System struct {
Text string `xml:",chardata"`
TrafficRegion string `xml:"TrafficRegion"`
InstallerName string `xml:"InstallerName"`
SerialNumber string `xml:"SerialNumber"`
LocationCode string `xml:"LocationCode"`
LocationDescription string `xml:"LocationDescription"`
OperatorCode string `xml:"OperatorCode"`
} `xml:"System"`
DecisionUnit struct {
Text string `xml:",chardata"`
Language string `xml:"Language"`
TimeZone string `xml:"TimeZone"`
DetectionDevice string `xml:"DetectionDevice"`
EnforcementMode string `xml:"EnforcementMode"`
TliChangeDelay struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"TliChangeDelay"`
GraceTime struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"GraceTime"`
SpeedLimit struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"SpeedLimit"`
CarSpeedLimit struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"CarSpeedLimit"`
TruckSpeedLimit struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"TruckSpeedLimit"`
SpeedOffset struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"SpeedOffset"`
CarSpeedOffset struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"CarSpeedOffset"`
TruckSpeedOffset struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"TruckSpeedOffset"`
SpeedThreshold struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"SpeedThreshold"`
CarSpeedThreshold struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"CarSpeedThreshold"`
TruckSpeedThreshold struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"TruckSpeedThreshold"`
} `xml:"DecisionUnit"`
TriggerArea TriggerArea `xml:"TriggerArea"`
} `xml:"EnforcementConfiguration"`
} `xml:"SystemInfo"`
Page []struct {
Text string `xml:",chardata"`
ID string `xml:"Id,attr"`
ImageInfo struct {
Text string `xml:",chardata"`
EMea struct {
Text string `xml:",chardata"`
PIdx string `xml:"PIdx"`
ETim struct {
Text string `xml:",chardata"`
JulS string `xml:"JulS"`
Usec string `xml:"usec"`
} `xml:"ETim"`
Date struct {
Text string `xml:",chardata"`
Pattern string `xml:"Pattern,attr"`
} `xml:"Date"`
Time struct {
Text string `xml:",chardata"`
Pattern string `xml:"Pattern,attr"`
} `xml:"Time"`
TakenDateTime string `xml:"TakenDateTime"`
YPos string `xml:"YPos"`
Lane string `xml:"Lane"`
VSpd struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"VSpd"`
UpperSpeedLimit struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"UpperSpeedLimit"`
LowerSpeedLimit struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"LowerSpeedLimit"`
OffnType string `xml:"OffnType"`
SORCode string `xml:"SORCode"`
RegMark string `xml:"RegMark"`
RegMarkHashValue string `xml:"RegMarkHashValue"`
ConfidentLevel string `xml:"ConfidentLevel"`
HighBitHashValue string `xml:"HighBitHashValue"`
LowBitHashValue string `xml:"LowBitHashValue"`
VideoHashValue string `xml:"VideoHashValue"`
VSLS string `xml:"VSLS"`
VDir string `xml:"VDir"`
VAcc string `xml:"VAcc"`
RCnf string `xml:"RCnf"`
VLen string `xml:"VLen"`
RTId string `xml:"RTId"`
REmu string `xml:"REmu"`
ImgNr string `xml:"ImgNr"`
TstR string `xml:"TstR"`
YTim struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"YTim"`
RTim struct {
Text string `xml:",chardata"`
Unit string `xml:"Unit,attr"`
} `xml:"RTim"`
OfId string `xml:"OfId"`
PhotoNo string `xml:"PhotoNo"`
PhotoCategory string `xml:"PhotoCategory"`
ReasonCode string `xml:"ReasonCode"`
ReasonType string `xml:"ReasonType"`
FiNa string `xml:"FiNa"`
OffT2 string `xml:"OffT2"`
IntV string `xml:"IntV"`
RDet string `xml:"RDet"`
PathHighBitImageA string `xml:"Path_High_Bit_Image_A"`
PathLowBitImageA string `xml:"Path_Low_Bit_Image_A"`
PathHighBitThumbnailA string `xml:"Path_High_Bit_Thumbnail_A"`
PathLowBitThumbnailA string `xml:"Path_Low_Bit_Thumbnail_A"`
PathHighBitImageB string `xml:"Path_High_Bit_Image_B"`
PathLowBitImageB string `xml:"Path_Low_Bit_Image_B"`
PathHighBitThumbnailB string `xml:"Path_High_Bit_Thumbnail_B"`
PathLowBitThumbnailB string `xml:"Path_Low_Bit_Thumbnail_B"`
PathHighBitImageC string `xml:"Path_High_Bit_Image_C"`
PathLowBitImageC string `xml:"Path_Low_Bit_Image_C"`
PathHighBitThumbnailC string `xml:"Path_High_Bit_Thumbnail_C"`
PathLowBitThumbnailC string `xml:"Path_Low_Bit_Thumbnail_C"`
PathHighBitImageD string `xml:"Path_High_Bit_Image_D"`
PathLowBitImageD string `xml:"Path_Low_Bit_Image_D"`
PathHighBitThumbnailD string `xml:"Path_High_Bit_Thumbnail_D"`
PathLowBitThumbnailD string `xml:"Path_Low_Bit_Thumbnail_D"`
PathHighBitImageE string `xml:"Path_High_Bit_Image_E"`
PathLowBitImageE string `xml:"Path_Low_Bit_Image_E"`
PathHighBitThumbnailE string `xml:"Path_High_Bit_Thumbnail_E"`
PathLowBitThumbnailE string `xml:"Path_Low_Bit_Thumbnail_E"`
PathHighBitImageF string `xml:"Path_High_Bit_Image_F"`
PathLowBitImageF string `xml:"Path_Low_Bit_Image_F"`
PathHighBitThumbnailF string `xml:"Path_High_Bit_Thumbnail_F"`
PathLowBitThumbnailF string `xml:"Path_Low_Bit_Thumbnail_F"`
PathVideo string `xml:"Path_Video"`
ViolationCaseSeq string `xml:"Violation_Case_Seq"`
MultipleCaseNo string `xml:"Multiple_Case_No"`
RadarTemplateNo string `xml:"Radar_Template_No"`
DefectInd string `xml:"Defect_Ind"`
} `xml:"EMea"`
} `xml:"ImageInfo"`
} `xml:"Page"`
}
type TriggerArea struct {
Text string `xml:",chardata"`
Point1X string `xml:"Point1X"`
Point1Y string `xml:"Point1Y"`
Point2X string `xml:"Point2X"`
Point2Y string `xml:"Point2Y"`
Point3X string `xml:"Point3X"`
Point3Y string `xml:"Point3Y"`
Point4X string `xml:"Point4X"`
Point4Y string `xml:"Point4Y"`
}
func (t TriggerArea) Line1() (*utils.Line, error) {
var line utils.Line
x1, err := strconv.Atoi(t.Point1X)
if err != nil {
return nil, err
}
y1, err := strconv.Atoi(t.Point1Y)
if err != nil {
return nil, err
}
x2, err := strconv.Atoi(t.Point2X)
if err != nil {
return nil, err
}
y2, err := strconv.Atoi(t.Point2Y)
if err != nil {
return nil, err
}
line.Start = utils.Point{X: x1, Y: y1}
line.End = utils.Point{X: x2, Y: y2}
return &line, nil
}
func (t TriggerArea) Line2() (*utils.Line, error) {
var line utils.Line
x1, err := strconv.Atoi(t.Point3X)
if err != nil {
return nil, err
}
y1, err := strconv.Atoi(t.Point3Y)
if err != nil {
return nil, err
}
x2, err := strconv.Atoi(t.Point4X)
if err != nil {
return nil, err
}
y2, err := strconv.Atoi(t.Point4Y)
if err != nil {
return nil, err
}
line.Start = utils.Point{X: x1, Y: y1}
line.End = utils.Point{X: x2, Y: y2}
return &line, nil
}
......@@ -9,9 +9,10 @@ import (
"image/jpeg"
"image/png"
"io"
"math"
"os"
"sort"
"github.com/llgcode/draw2d/draw2dimg"
)
type Point struct {
......@@ -49,7 +50,7 @@ var ColorMap = map[string]color.RGBA{
func NewDrawLineOptions() *DrawLineOptions {
// default value
return &DrawLineOptions{
shape: ShapeLine,
shape: ShapeQuad,
thickness: 1,
color: "pink",
}
......@@ -73,112 +74,15 @@ func WithColor(color string) func(*DrawLineOptions) {
}
}
func drawLine(img draw.Image, c color.Color, thickness, x1, y1, x2, y2 int) draw.Image {
dx := math.Abs(float64(x2 - x1))
dy := math.Abs(float64(y2 - y1))
directionX := false
directionY := false
sx, sy := 1, 1
if x1 >= x2 {
sx = -1
}
if y1 >= y2 {
sy = -1
}
faultBit := dx - dy
for {
if directionX {
for i := 0; i < thickness; i++ {
if i%2 == 0 {
img.Set(x1, y1+i/2, c)
} else {
img.Set(x1, y1-i/2, c)
}
}
}
if directionY {
for i := 0; i < thickness; i++ {
if i%2 == 0 {
img.Set(x1+i/2, y1, c)
} else {
img.Set(x1-i/2, y1, c)
}
}
}
if !directionX && !directionY {
img.Set(x1, y1, c)
}
if x1 == x2 && y1 == y2 {
return img
}
faultBit2 := 2 * faultBit
if faultBit2 > -dy {
faultBit -= dy
x1 += sx
directionX = true
} else {
directionX = false
}
if faultBit2 < dx {
faultBit += dx
y1 += sy
directionY = true
} else {
directionY = false
}
}
}
func getImageFromFilePath(f io.ReadSeeker) (draw.Image, error) {
buffer := make([]byte, 512)
_, err := f.Read(buffer)
if err != nil {
return nil, fmt.Errorf("failed to read file header: %w", err)
}
_, err = f.Seek(0, 0)
if err != nil {
return nil, fmt.Errorf("failed to reset file pointer: %w", err)
}
format := ""
if bytes.HasPrefix(buffer, []byte("\xFF\xD8\xFF")) {
format = "jpeg"
} else if bytes.HasPrefix(buffer, []byte("\x89PNG\r\n\x1a\n")) {
format = "png"
} else {
return nil, fmt.Errorf("unsupported image format")
}
var img image.Image
switch format {
case "jpeg":
img, err = jpeg.Decode(f)
case "png":
img, err = png.Decode(f)
}
if err != nil {
return nil, fmt.Errorf("failed to decode image: %w", err)
}
bounds := img.Bounds()
rgbaImg := image.NewRGBA(bounds)
draw.Draw(rgbaImg, bounds, img, bounds.Min, draw.Src)
return rgbaImg, nil
}
func OutputImage(img draw.Image, name string) error {
outputFile, err := os.Create(name)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
png.Encode(outputFile, img)
jpeg.Encode(outputFile, img, &jpeg.Options{
Quality: 80,
})
outputFile.Close()
return nil
}
......@@ -225,3 +129,56 @@ func DrawLines(
return nil, fmt.Errorf("unsupported shape: %s", opts.shape)
}
}
func drawLine(img draw.Image, c color.Color, thickness, x1, y1, x2, y2 int) draw.Image {
gc := draw2dimg.NewGraphicContext(img)
gc.SetStrokeColor(c)
gc.SetLineWidth(float64(thickness))
gc.MoveTo(float64(x1), float64(y1))
gc.LineTo(float64(x2), float64(y2))
gc.Stroke()
return img
}
func getImageFromFilePath(f io.ReadSeeker) (draw.Image, error) {
buffer := make([]byte, 512)
_, err := f.Read(buffer)
if err != nil {
return nil, fmt.Errorf("failed to read file header: %w", err)
}
_, err = f.Seek(0, 0)
if err != nil {
return nil, fmt.Errorf("failed to reset file pointer: %w", err)
}
format := ""
if bytes.HasPrefix(buffer, []byte("\xFF\xD8\xFF")) {
format = "jpeg"
} else if bytes.HasPrefix(buffer, []byte("\x89PNG\r\n\x1a\n")) {
format = "png"
} else {
return nil, fmt.Errorf("unsupported image format")
}
var img image.Image
switch format {
case "jpeg":
img, err = jpeg.Decode(f)
case "png":
img, err = png.Decode(f)
}
if err != nil {
return nil, fmt.Errorf("failed to decode image: %w", err)
}
bounds := img.Bounds()
rgbaImg := image.NewRGBA(bounds)
draw.Draw(rgbaImg, bounds, img, bounds.Min, draw.Src)
return rgbaImg, nil
}