I have been using the library draw2d with go-nexrad, and I am able to generate PNG and SVG files. However, the SVG files that are generated are enormous, with a less than 10MB PNG file becoming a 28MB SVG file with the same options. Here is the code:
func render(out string, radials []*archive2.Message31, label string) {
width := float64(imageSize)
height := float64(imageSize)
PNGcanvas := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
draw.Draw(PNGcanvas, PNGcanvas.Bounds(), image.Black, image.ZP, draw.Src)
PNGgc := draw2dimg.NewGraphicContext(PNGcanvas)
SVGcanvas := draw2dsvg.NewSvg()
SVGcanvas.Width = strconv.Itoa(int(width)) + "px"
SVGcanvas.Height = strconv.Itoa(int(width)) + "px"
SVGgc := draw2dsvg.NewGraphicContext(SVGcanvas)
xc := width / 2
yc := height / 2
pxPerKm := width / 2 / 460
firstGatePx := float64(radials[0].ReflectivityData.DataMomentRange) / 1000 * pxPerKm
gateIntervalKm := float64(radials[0].ReflectivityData.DataMomentRangeSampleInterval) / 1000
gateWidthPx := gateIntervalKm * pxPerKm
t := time.Now()
log.Println("rendering radials")
// valueDist := map[float32]int{}
for _, radial := range radials {
// round to the nearest rounded azimuth for the given resolution.
// ex: for radial 20.5432, round to 20.5
azimuthAngle := float64(radial.Header.AzimuthAngle) - 90
if azimuthAngle < 0 {
azimuthAngle = 360.0 + azimuthAngle
}
azimuthSpacing := radial.Header.AzimuthResolutionSpacing()
azimuth := math.Floor(azimuthAngle)
if math.Floor(azimuthAngle+azimuthSpacing) > azimuth {
azimuth += azimuthSpacing
}
startAngle := azimuth * (math.Pi / 180.0) /* angles are specified */
endAngle := azimuthSpacing * (math.Pi / 180.0) /* clockwise in radians */
// start drawing gates from the start of the first gate
distanceX, distanceY := firstGatePx, firstGatePx
if vectorize == "png" {
PNGgc.SetLineWidth(gateWidthPx + 1)
} else if vectorize == "svg" {
SVGgc.SetLineWidth(gateWidthPx + 1)
}
if vectorize == "png" {
PNGgc.SetLineCap(draw2d.ButtCap)
} else if vectorize == "svg" {
SVGgc.SetLineCap(draw2d.ButtCap)
}
var gates []float32
switch product {
case "vel":
gates = radial.VelocityData.ScaledData()
case "sw":
gates = radial.SwData.ScaledData()
case "rho":
gates = radial.RhoData.ScaledData()
default:
gates = radial.ReflectivityData.ScaledData()
}
numGates := len(gates)
for i, v := range gates {
if v != archive2.MomentDataBelowThreshold {
//fmt.Println(gateWidthPx)
if i == 0 {
SVGgc.SetLineWidth(0)
} else if i > 0 {
SVGgc.SetLineWidth(gateWidthPx + 1)
}
// valueDist[v] += 1
if vectorize == "png" {
PNGgc.MoveTo(xc+math.Cos(startAngle)*distanceX, yc+math.Sin(startAngle)*distanceY)
} else if vectorize == "svg" {
SVGgc.MoveTo(xc+math.Cos(startAngle)*distanceX, yc+math.Sin(startAngle)*distanceY)
}
// make the gates connect visually by extending arcs so there is no space between adjacent gates.
if i == 0 {
if vectorize == "png" {
PNGgc.ArcTo(xc, yc, distanceX, distanceY, startAngle-.001, endAngle+.001)
} else if vectorize == "svg" {
SVGgc.ArcTo(xc, yc, distanceX, distanceY, startAngle-.001, endAngle+.001)
}
} else if i == numGates-1 {
if vectorize == "png" {
PNGgc.ArcTo(xc, yc, distanceX, distanceY, startAngle, endAngle)
} else if vectorize == "svg" {
SVGgc.ArcTo(xc, yc, distanceX, distanceY, startAngle, endAngle)
}
} else {
if vectorize == "png" {
PNGgc.ArcTo(xc, yc, distanceX, distanceY, startAngle, endAngle+.001)
} else if vectorize == "svg" {
SVGgc.ArcTo(xc, yc, distanceX, distanceY, startAngle, endAngle+.001)
}
}
if vectorize == "png" {
PNGgc.SetStrokeColor(colorSchemes[product][colorScheme](v))
} else if vectorize == "svg" {
SVGgc.SetStrokeColor(colorSchemes[product][colorScheme](v))
}
if vectorize == "png" {
PNGgc.Stroke()
} else if vectorize == "svg" {
SVGgc.Stroke()
}
}
distanceX += gateWidthPx
distanceY += gateWidthPx
azimuth += radial.Header.AzimuthResolutionSpacing()
}
}
// fmt.Println(valueDist)
if renderLabel {
if vectorize == "png" {
addLabel(PNGcanvas, int(width-495.0), int(height-10.0), label)
} else if vectorize == "svg" {
logrus.Warn("Labels cannot be drawn on an SVG image, ignoring -L flag")
}
}
// Save to file
if vectorize == "png" {
draw2dimg.SaveToPngFile(out, PNGcanvas)
fmt.Println("Finished in", time.Since(t))
} else if vectorize == "svg" {
draw2dsvg.SaveToSvgFile(out, SVGcanvas)
fmt.Println("Finished in", time.Since(t))
}
}
The full file can be found in my fork of the project here.
The reason I think the SVG is so large is because it is generating the file very inefficiently, possibly by trying to render every pixel instead of just a start and end point. I have tried setting the DPI with SVGgc.setDPI(), but that hasn't worked.
If anyone has any idea about why the file is so large, or any idea of how to fix it, I would greatly appreciate your input. Hopefully you won't have to go through the entire go-nexrad project to understand this, I have included the code block that I am almost certain is causing the issue, and is the part that uses the library.
Related
In p5.js, I am using "new p5.FFT(x,y)" to analyze the amplifer mp3 file.
But this has a little problem that if you set the mp3's volume to 0(by using .setVolume(x)) the song file cannot be analyzed maybe because you set volume to 0 so there's no input.
So i want to know how to analyze songfile even when i set the volume to 0.
The trick is you need to connect your FFT at a point where the volume is still non-zero, and then have a node down stream where you control the volume. Here's an example where I've used the p5.EQ effect to control the volume of one part of the audio graph. The "Tada" sound is connected to an FFT and to the "mute" p5.EQ effect. This makes it so that the FFT visualizes the sound at full volume, but the slider controls how loud the sound actually is. The "Ding" sound on the other hand is connected directly to the output, no FFT, no volume control.
let tada, ding;
let tadaBtn, dingBtn;
let volSlider;
let fft;
let mute;
function preload() {
tada = loadSound('https://www.paulwheeler.us/files/TADA.WAV');
ding = loadSound('https://www.paulwheeler.us/files/DING.WAV');
}
function setup() {
createCanvas(windowWidth, windowHeight);
let controls = createElement('div');
controls.style('display', 'flex');
controls.position(10, 10);
tadaBtn = createButton('Tada');
tadaBtn.mouseClicked(() => {
if (!tada.isPlaying()) {
tada.play();
tadaBtn.html('Stop');
} else {
tada.stop();
}
});
tadaBtn.parent(controls);
tada.onended(() => {
tadaBtn.html('Tada');
});
dingBtn = createButton('Ding');
dingBtn.mouseClicked(() => {
if (!ding.isPlaying()) {
ding.play();
dingBtn.html('Stop');
} else {
ding.stop();
}
});
dingBtn.parent(controls);
ding.onended(() => {
dingBtn.html('Ding');
});
volSlider = createSlider(0, 1, 0, 0);
volSlider.input(() => {
mute.amp(volSlider.value());
});
volSlider.parent(controls);
tada.disconnect();
fft = new p5.FFT();
fft.setInput(tada);
mute = new p5.EQ();
mute.amp(volSlider.value());
tada.connect(mute);
mute.connect();
}
function draw() {
background(0);
drawSpectrumGraph(0, 0, width, height);
}
// Graphing code adapted from https://jankozeluh.g6.cz/index.html by Jan Koželuh
function drawSpectrumGraph(left, top, w, h) {
let spectrum = fft.analyze();
stroke('limegreen');
fill('darkgreen');
strokeWeight(1);
beginShape();
vertex(left, top + h);
for (let i = 0; i < spectrum.length; i++) {
vertex(
left + map(log(i), 0, log(spectrum.length), 0, w),
top + map(spectrum[i], 0, 255, h, 0)
);
}
vertex(left + w, top + h);
endShape(CLOSE);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>
I'm trying to solve this one but I am stumped. When I continuously hover over an object, the ram usage increases. The hover code is below:
//Small memory leak here. Need to figure this out.
void PlayerInteraction::InteractionControllerHover(std::string interactionMessage) {
SDL_DestroyTexture(Scene1::ftexture);
SDL_FreeSurface(Scene1::fsurface);
const char* im = interactionMessage.c_str();
if (interactionMessage != "" ) {
int interactionMessagelength = interactionMessage.length();
Scene1::textRect = { 500, 610, interactionMessagelength * 10, 20 };
Scene1::fsurface = TTF_RenderText_Solid(Scene1::font, im, Scene1::fcolor);
Scene1::ftexture = SDL_CreateTextureFromSurface(Scene1::renderer, Scene1::fsurface);
//_sleep(70) seems to slow the leak down.
}
}
if I add _sleep(70) to the code, the memory leak is much slower, however I can't figure out why its leaking because I am destroying the texture each time the hover occurs. I don't like adding sleep because it causes other issues. Any advice?
//Mouse Hover Game Interaction.
case SDL_MOUSEMOTION:
//Event Motion coordinates. Where the mouse moves on the screen.
x = event.motion.x;
y = event.motion.y;
gd = gdSprite.x;
gy = gdSprite.y;
//This addresses the movement to the left issue where the player never reaches to destination and prevents hover interaction.
if (playerMessage != true && interactionMessage == "") {
//Prevents sleep from kicking in when walking to a target.
if (gdSprite.x < gd && gdSprite.y < y || gdSprite.x > gd && gdSprite.y > y) {
ftexture = SDL_CreateTextureFromSurface(renderer, fsurface); //Potentially fixes sprite appearing in text.
SDL_DestroyTexture(ftexture);
playerIsMoving = 0;
}
else {
ftexture = SDL_CreateTextureFromSurface(renderer,fsurface); //Potentially fixes sprite appearing in text.
SDL_DestroyTexture(ftexture);
SDL_DestroyTexture(Textures::spriteTexture);
Textures::spriteTexture = SDL_CreateTextureFromSurface(renderer, Textures::spriteDown1); //Makes player face you when you are hovering.
}
interactionMessage = pob.HoverObjects(x, y, scene, gd, gy);
}
if (interactionMessage != "" && playerIsMoving !=1) {
_sleep(70); //reduces memory leak when repeatedly hovering over objects in quick succession.
pi.InteractionControllerHover(interactionMessage);
}
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym)
{
case SDLK_ESCAPE:
case SDLK_q:
gameover = 1;
break;
}
break;
}
}
Thanks for the advice. This seems to have fixed the problem:
void PlayerInteraction::InteractionControllerHover(std::string interactionMessage) {
const char* im = interactionMessage.c_str();
if (interactionMessage != "" ) {
int interactionMessagelength = interactionMessage.length();
Scene1::textRect = { 500, 610, interactionMessagelength * 10, 20 }; // The * 10, 20 is a mathematical way of setting the text width depending on the length of the text.
Scene1::fsurface = TTF_RenderText_Solid(Scene1::font, im, Scene1::fcolor);
Scene1::ftexture = SDL_CreateTextureFromSurface(Scene1::renderer, Scene1::fsurface);
SDL_FreeSurface(Scene1::fsurface);
}
//Mouse Hover Game Interaction.
case SDL_MOUSEMOTION:
//Event Motion coordinates. Where the mouse moves on the screen.
x = event.motion.x;
y = event.motion.y;
gd = gdSprite.x;
gy = gdSprite.y;
//This addresses the movement to the left issue where the player never reaches to destination and prevents hover interaction.
if (playerMessage != true && interactionMessage == "") {
//Prevents sleep from kicking in when walking to a target.
if (gdSprite.x < gd && gdSprite.y < y || gdSprite.x > gd && gdSprite.y > y) {
SDL_DestroyTexture(ftexture);
playerIsMoving = 0;
}
else {
SDL_DestroyTexture(ftexture);
SDL_DestroyTexture(Textures::spriteTexture);
Textures::spriteTexture = SDL_CreateTextureFromSurface(renderer, Textures::spriteDown1); //Makes player face you when you are hovering.
}
interactionMessage = pob.HoverObjects(x, y, scene, gd, gy);
}
if (interactionMessage != "" && playerIsMoving !=1) {
pi.InteractionControllerHover(interactionMessage);
}
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym)
{
case SDLK_ESCAPE:
case SDLK_q:
gameover = 1;
break;
}
break;
}
}
I am working on a 2d grid with scale touch functionality. I've managed to set the translate boundaries so that the screen viewport doesn't go beyond the grid boundaries. I'm now struggling with the algorithm for determining the new translate values when scaling on both two finger touch and mouse wheel events.
touchStarted sets the vector angle between the two initial touches. lastTouchAngle is for comparison in touchMoved.
function touchStarted() {
if(touches.length == 2) {
let touchA = createVector(touches[0].x, touches[0].y);
let touchB = createVector(touches[1].x, touches[1].y);
lastTouchAngle = touchA.angleBetween(touchB);
}
return false;
}
touchMoved makes the current touches vectors, compares the angle, and then scales accordingly.
t_MinX and t_MinY set the lowest possible translate value for the constrains, but determining what the new translate value should be is where I'm lost. I know it's going to require the current scale, the center point between the two touches, and the width and height of the Canvas.
function touchMoved() {
if(touches.length == 1) {
panTranslate(translateX, translateY, mouseX, mouseY, pmouseX, pmouseY);
} else if (touches.length == 2) {
let touchA = createVector(touches[0].x, touches[0].y);
let touchB = createVector(touches[1].x, touches[1].y);
scl = (abs(lastTouchAngle) < abs(touchA.angleBetween(touchB)) ? (scl+sclStep < sclMax ? scl+sclStep : sclMax) : (scl-sclStep > sclMin ? scl-sclStep : sclMin));
let t_MinX = (screenH/sclMin) * (sclMin-scl);
let t_MinY = (screenW/sclMin) * (sclMin-scl);
let tX = translateX;
let tY = translateY;
if(abs(lastTouchAngle) > abs(touchA.angleBetween(touchB))) {
console.log("Scale out");
translateX = constrain(tX+mX, t_MinX, 0);
translateY = constrain(tY+mY, t_MinY, 0);
} else {
console.log("Scale in");
if(scl != sclMax) {
translateX = constrain(tX-mX, t_MinX, 0);
translateY = constrain(tY-mY, t_MinY, 0);
}
}
// Set current touch angle to lastTouchAngle
lastTouchAngle = touchA.angleBetween(touchB);
}
return false;
}
Here is the bit getting me confused:
translateX = constrain(tX+mX, t_MinX, 0);
translateY = constrain(tY+mY, t_MinY, 0);
Full code: https://editor.p5js.org/OMTI/sketches/9ux6Rq6n5
https://stackoverflow.com/questions/5713174
I found the answer at the above link and was able to get this working from the answer there.
I'm using the google.golang.org/api/sheets/v4 Golang package and can successfully update a Google Sheet using the API. However, I want to set the background color of a row if it meets a specific criteria. I'm finding that I cannot set the background color of one row different from the rest and I don't know why. Here is a code snippet
func (bs *BudgetSheet) populateCells() []*sheets.RowData {
rows := []*sheets.RowData{}
bgGreen := &sheets.Color{
Alpha: 1,
Blue: 0,
Red: 0,
Green: 1,
}
bgWhite := &sheets.Color{
Alpha: 1,
Blue: 1,
Red: 1,
Green: 1,
}
for _, csvRow := range bs.CSV {
if csvRow.Name == "Credit Card Payment" {
continue
}
cells := []*sheets.CellData{}
row := &sheets.RowData{}
cells = append(cells, mkNumberCell(4, centerAlign))
cells = append(cells, mkStringCell(csvRow.Source, centerAlign))
cells = append(cells, mkDateCell(csvRow.Date))
cells = append(cells, mkStringCell(csvRow.Name, leftAlign))
if csvRow.Source == "-" {
if csvRow.Amount < 0 {
cells = append(cells, mkNumberCell(-1*csvRow.Amount, dollarsCell))
cells = append(cells, mkStringCell("", leftAlign))
} else {
cells = append(cells, mkStringCell("", leftAlign))
cells = append(cells, mkNumberCell(csvRow.Amount, dollarsCell))
}
cells = append(cells, mkStringCell("", leftAlign))
} else {
cells = append(cells, mkStringCell("", leftAlign))
cells = append(cells, mkStringCell("", leftAlign))
cells = append(cells, mkNumberCell(-1*csvRow.Amount, dollarsCell))
}
if csvRow.Name == "Salary" {
for i := range cells {
cells[i].UserEnteredFormat.BackgroundColor = bgGreen
}
} else {
for i := range cells {
cells[i].UserEnteredFormat.BackgroundColor = bgWhite
}
}
row.Values = cells
rows = append(rows, row)
emptyCells := []*sheets.CellData{}
// cells = append(cells, mkStringCell("", centerAlign))
emptyRow := &sheets.RowData{
Values: emptyCells,
}
rows = append(rows, emptyRow)
}
return rows
}
func (bs *BudgetSheet) updateRows() {
requests := []*sheets.Request{}
rows := bs.populateCells()
gc := &sheets.GridCoordinate{
SheetId: bs.ID,
RowIndex: bs.FirstRowToUpdate,
ColumnIndex: 0,
}
updateCellsRequest := sheets.UpdateCellsRequest{
Fields: "*",
Rows: rows,
Start: gc,
}
request := sheets.Request{
UpdateCells: &updateCellsRequest,
}
requests = append(requests, &request)
// create the batch request
batchUpdateRequest := sheets.BatchUpdateSpreadsheetRequest{
Requests: requests,
}
// execute the request
_, err := bs.Service.Spreadsheets.BatchUpdate(bs.SpreadsheetID, &batchUpdateRequest).Do()
if err != nil {
log.Fatalf("could not perform update action: %v\n", err)
}
}
The lines in question are
if csvRow.Name == "Salary" {
for i := range cells {
cells[i].UserEnteredFormat.BackgroundColor = bgGreen
}
} else {
for i := range cells {
cells[i].UserEnteredFormat.BackgroundColor = bgWhite
}
}
My input results in one row meeting the criteria above an successfully sets the background color of all cells in that row to green. However, when I view the Google Sheet, all rows have a background color of white. Any suggestions?
Thanks!
Well, I solved the problem though I'm not exactly sure why. I'm pretty sure that the problem was using
cells[i].UserEnteredFormat.BackgroundColor = bgGreen
instead of defining the cells outright as in
func formatCell(align, colorName string, bordersOn bool) *sheets.CellFormat {
return &sheets.CellFormat{
HorizontalAlignment: strings.ToUpper(align),
TextFormat: font(),
BackgroundColor: color(colorName),
Borders: borders(bordersOn),
}
}
In any case, the program works perfectly now and I'm perfectly happy. FWIW, the purpose of the program is the read the CSV transactions files downloaded from my checking and credit card institutions and update my register spreadsheet. While it doesn't save me a great deal of time each weekend, it was lots of fun to write. And a shout out to #Jacques-GuzelHeron for his attempt to help.
I search on my friend Google for some code to do smooth scroll and found this : Smooth vertical scrolling on mouse wheel in vanilla javascript?
It works well but if i scroll once and then try to use my mouse to manually move the scrollbar, it's broken...
SmoothScroll(document, 120, 12);
function SmoothScroll(target, speed, smooth) {
if (target === document)
target = (document.scrollingElement ||
document.documentElement ||
document.body.parentNode ||
document.body) // cross browser support for document scrolling
var moving = false
var pos = target.scrollTop
var frame = target === document.body &&
document.documentElement ?
document.documentElement :
target // safari is the new IE
target.addEventListener('scroll', scrolled, {
passive: false
})
target.addEventListener('mousewheel', scrolled, {
passive: false
})
target.addEventListener('DOMMouseScroll', scrolled, {
passive: false
})
function scrolled(e) {
e.preventDefault(); // disable default scrolling
var delta = normalizeWheelDelta(e)
pos += -delta * speed
pos = Math.max(0, Math.min(pos, target.scrollHeight - frame.clientHeight)) // limit scrolling
if (!moving) update()
}
function normalizeWheelDelta(e) {
if (e.detail) {
if (e.wheelDelta)
return e.wheelDelta / e.detail / 40 * (e.detail > 0 ? 1 : -1) // Opera
else
return -e.detail / 3 // Firefox
} else
return e.wheelDelta / 120 // IE,Safari,Chrome
}
function update() {
moving = true
var delta = (pos - target.scrollTop) / smooth
target.scrollTop += delta
if (Math.abs(delta) > 0.5)
requestFrame(update)
else
moving = false
}
var requestFrame = function () { // requestAnimationFrame cross browser
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (func) {
window.setTimeout(func, 1000 / 50);
}
);
}()
}
So... i want it to work properly when i already scroll once but try to use the mouse to move the scrollbar instead of mousewheel.
Thanks for helping!
Looks like you could fix it by re-adjusting the pos variable to the scrollTop before your scrolling calculations.
Additionally theres a bug where your scroll could get stuck in an infinite render loop causing you to never stop animating. This was due to the delta being .5 < delta < 1 making the request frame get called forever. You cant actually move the scrollTop anything less than 1 so I adjusted the conditions for another render loop and rounded the delta
function scrolled(e) {
// if currently not animating make sure our pos is up to date with the current scroll postion
if(!moving) {
pos = target.scrollTop;
}
e.preventDefault(); // disable default scrolling
var delta = normalizeWheelDelta(e)
pos += -delta * speed
pos = Math.max(0, Math.min(pos, target.scrollHeight - frame.clientHeight)) // limit scrolling
if (!moving) update()
}
function update() {
moving = true;
// scrollTop is an integer and moving it by anything less than a whole number wont do anything
// to prevent a noop and an infinite loop we need to round it
var delta = absRound((pos - target.scrollTop) / smooth)
target.scrollTop += delta
if (Math.abs(delta) >= 1) {
requestFrame(update)
} else {
moving = false
}
}
function absRound(num) {
if(num < 0) {
return -1*Math.round(-1*num);
} else {
return Math.round(num);
}
}
That way when manually adjusting the scroll position if the wheel is used it doesnt jump to the position it was once at, but instead adjust itself to the current scroll position.