High quality graph/waveform display component in C# - c#

I'm looking for a fast, professionally looking and customizable waveform display component in C#.
I'm wanting to display mainly real-time audio waveforms (fast!) in both time and frequency domain. I would like the ability to zoom, change axis settings, display multiple channels, customize the feel and colors etc...
Anybody knows of anything, whether commercial or not?
Thank you!
Diego

I bumped into a code project awhile ago that was doing this.
Check out http://www.codeproject.com/KB/miscctrl/GraphComponents.aspx it may be what you are looking for to do real-time graphing in .net

as far as i know, national instrument has some cool control, but it's not free.
http://sine.ni.com/psp/app/doc/p/id/psp-317
free ones:
http://www.codeproject.com/KB/audio-video/wavecontrol.aspx

Based on Illaya's code:
public void CreateWaveForm(string audioFilePath, string audioWaveFormFilePath)
{
try
{
int bytesPerSample = 0;
using (NAudio.Wave.Mp3FileReader reader = new NAudio.Wave.Mp3FileReader(audioFilePath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf)))
{
using (NAudio.Wave.WaveChannel32 channelStream = new NAudio.Wave.WaveChannel32(reader))
{
bytesPerSample = (reader.WaveFormat.BitsPerSample / 8) * channelStream.WaveFormat.Channels;
//Give a size to the bitmap; either a fixed size, or something based on the length of the audio
using (Bitmap bitmap = new Bitmap((int)Math.Round(reader.TotalTime.TotalSeconds * 40), 200))
{
int width = bitmap.Width;
int height = bitmap.Height;
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.White);
Pen bluePen = new Pen(Color.Blue);
int samplesPerPixel = (int)(reader.Length / (double)(width * bytesPerSample));
int bytesPerPixel = bytesPerSample * samplesPerPixel;
int bytesRead;
byte[] waveData = new byte[bytesPerPixel];
for (float x = 0; x < width; x++)
{
bytesRead = reader.Read(waveData, 0, bytesPerPixel);
if (bytesRead == 0)
break;
short low = 0;
short high = 0;
for (int n = 0; n < bytesRead; n += 2)
{
short sample = BitConverter.ToInt16(waveData, n);
if (sample < low) low = sample;
if (sample > high) high = sample;
}
float lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue);
float highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue);
float lowValue = height * lowPercent;
float highValue = height * highPercent;
graphics.DrawLine(bluePen, x, lowValue, x, highValue);
}
}
bitmap.Save(audioWaveFormFilePath);
}
}
}
}
catch
{
}
}

That is a wave flow displayer
http://www.codeproject.com/KB/audio-video/wavecontrol.aspx

Check out Zedgraph. It's a free graphing library that works great. There are lots of code samples on their website that allow you to do what you're asking. Zedgraph Downloads Their website seems to be having issues right now, but the download session works and contains all of their sample files.

This will generate waveform from audio file using nAudio...
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string strPath = Server.MapPath("audio/060.mp3");
string SongID = "2";
byte[] bytes = File.ReadAllBytes(strPath);
WriteToFile(SongID,strPath, bytes);
Response.Redirect("Main.aspx");
}
private void WriteToFile(string SongID, string strPath, byte[] Buffer)
{
try
{
int samplesPerPixel = 128;
long startPosition = 0;
//FileStream newFile = new FileStream(GeneralUtils.Get_SongFilePath() + "/" + strPath, FileMode.Create);
float[] data = FloatArrayFromByteArray(Buffer);
Bitmap bmp = new Bitmap(1170, 200);
int BORDER_WIDTH = 5;
int width = bmp.Width - (2 * BORDER_WIDTH);
int height = bmp.Height - (2 * BORDER_WIDTH);
NAudio.Wave.Mp3FileReader reader = new NAudio.Wave.Mp3FileReader(strPath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf));
NAudio.Wave.WaveChannel32 channelStream = new NAudio.Wave.WaveChannel32(reader);
int bytesPerSample = (reader.WaveFormat.BitsPerSample / 8) * channelStream.WaveFormat.Channels;
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.White);
Pen pen1 = new Pen(Color.Gray);
int size = data.Length;
string hexValue1 = "#009adf";
Color colour1 = System.Drawing.ColorTranslator.FromHtml(hexValue1);
pen1.Color = colour1;
Stream wavestream = new NAudio.Wave.Mp3FileReader(strPath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf));
wavestream.Position = 0;
int bytesRead1;
byte[] waveData1 = new byte[samplesPerPixel * bytesPerSample];
wavestream.Position = startPosition + (width * bytesPerSample * samplesPerPixel);
for (float x = 0; x < width; x++)
{
short low = 0;
short high = 0;
bytesRead1 = wavestream.Read(waveData1, 0, samplesPerPixel * bytesPerSample);
if (bytesRead1 == 0)
break;
for (int n = 0; n < bytesRead1; n += 2)
{
short sample = BitConverter.ToInt16(waveData1, n);
if (sample < low) low = sample;
if (sample > high) high = sample;
}
float lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue);
float highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue);
float lowValue = height * lowPercent;
float highValue = height * highPercent;
g.DrawLine(pen1, x, lowValue, x, highValue);
}
}
string filename = Server.MapPath("image/060.png");
bmp.Save(filename);
bmp.Dispose();
}
catch (Exception e)
{
}
}
public float[] FloatArrayFromStream(System.IO.MemoryStream stream)
{
return FloatArrayFromByteArray(stream.GetBuffer());
}
public float[] FloatArrayFromByteArray(byte[] input)
{
float[] output = new float[input.Length / 4];
for (int i = 0; i < output.Length; i++)
{
output[i] = BitConverter.ToSingle(input, i * 4);
}
return output;
}
}

Related

Remove gray from the photo c# windows forms

I want to remove grayness from the picture in pictureBox1, as in the first photo below. The second photo shows the formulas that can be used to do this. I wrote an approximate code, but the compiler throws an error, that variables red1, green1 and blue1 cannot be written to Color newPixel due to type incompatibility. Please help me fixing my code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void pictureBox1_Click(object sender, EventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Title = "Відкрити зображення";
dlg.Filter = "jpg files (*.jpg)|*.jpg|All filles (*.*)|*.*";
if (dlg.ShowDialog() == DialogResult.OK)
{
pictureBox1.Image = new Bitmap(dlg.OpenFile());
pictureBox1.Height = pictureBox1.Image.Height;
pictureBox1.Width = pictureBox1.Image.Width;
}
dlg.Dispose();
label1.Visible = false;
}
private void button1_Click(object sender, EventArgs e)
{
Bitmap input = new Bitmap(pictureBox1.Image);
Bitmap output = new Bitmap(input.Width, input.Height);
int[] red = new int[256];
int[] green = new int[256];
int[] blue = new int[256];
for (int x = 0; x < input.Width; x++)
{
for (int y = 0; y < input.Height; y++)
{
Color pixel = ((Bitmap)pictureBox1.Image).GetPixel(x, y);
red[pixel.R]++;
green[pixel.G]++;
blue[pixel.B]++;
double Ri = red.Average();
double Gi = green.Average();
double Bi = blue.Average();
double Avg = (Ri+Bi+Gi)/3;
double red1 = red (Avg/Ri);
double green1 = green(Avg / Gi);
double blue1 = blue(Avg / Bi);
Color newPixel = ((Color)red1| (Color)green1 | (Color)blue1);
output.SetPixel(x, y, Color.((int)newPixel));
}
}
pictureBox2.Image = output;
}
}
}
You should use Color.FromArgb() to create the new color. However, this method expects 3 int as input and not double, so you need to convert your doubles to integers.
Simple type cast - (int)someDouble
This solution will simply remove any decimals from the double value (1.9 => 1):
double red1 = 123.45;
double green1 = 12.345;
double blue1 = 234.56;
Color newPixel = Color.FromArgb((int)red1, (int)green1, (int)blue1);
// R=123, G=12, B=234
Math.Round(someDouble)
This solution will round the double value to the nearest integer (1.5 => 2 and 1.49 => 1):
int red1 = (int)Math.Round(123.45);
int green1 = (int)Math.Round(12.345);
int blue1 = (int)Math.Round(234.56);
Color newPixel = Color.FromArgb(red1, green1, blue1);
// R=123, G=12, B=235
You misinterpreted the formula. N in the formula specifies the total number of the pixels in the image. The overlined letters means the average channel value over the image. These variable should be the type of double. I called them redAverage, greenAverage and blueAverage, respectively. And as you
already know, Avg is the average of these variables. Finally, R', G', B' are the new channel values calculated using the old values.
using System.Drawing.Imaging;
namespace Convert2Gray
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private Bitmap m_inputImage;
private Bitmap m_outputImage;
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
m_inputImage = (Bitmap)Image.FromFile("sample.png");
m_outputImage = new Bitmap(m_inputImage.Width, m_inputImage.Height, m_inputImage.PixelFormat);
pictureBox1.Image = m_inputImage;
pictureBox1.SizeMode = PictureBoxSizeMode.AutoSize;
DoConversion();
pictureBox2.Image = m_outputImage;
pictureBox2.SizeMode = PictureBoxSizeMode.AutoSize;
}
private unsafe void DoConversion()
{
BitmapData inputBitmapData = m_inputImage.LockBits(new Rectangle(0, 0, m_inputImage.Width, m_inputImage.Height), ImageLockMode.ReadWrite, m_inputImage.PixelFormat);
BitmapData outputBitmapData = m_outputImage.LockBits(new Rectangle(0, 0, m_outputImage.Width, m_outputImage.Height), ImageLockMode.ReadWrite, m_outputImage.PixelFormat);
byte* inputScan0 = (byte*)inputBitmapData.Scan0;
byte* outputScan0 = (byte*)outputBitmapData.Scan0;
int inputStride = inputBitmapData.Stride;
int outputStride = outputBitmapData.Stride;
int bytesPerPixel = Image.GetPixelFormatSize(m_inputImage.PixelFormat) / 8;
double redAverage = 0.0;
double greenAverage = 0.0;
double blueAverage = 0.0;
double average = 0.0;
int pixelCount = m_inputImage.Width * m_inputImage.Height;
for (int y = 0; y < m_inputImage.Height; y++)
{
byte* inputCurrentRow = inputScan0 + y * inputStride;
byte* outputCurrentRow = outputScan0 + y * outputStride;
for (int x = 0; x < m_inputImage.Width; x++)
{
ColorBgr* inputColor = (ColorBgr*)(inputCurrentRow + x * bytesPerPixel);
redAverage += inputColor->R;
greenAverage += inputColor->G;
blueAverage += inputColor->B;
}
}
redAverage /= pixelCount;
greenAverage /= pixelCount;
blueAverage /= pixelCount;
average = (redAverage + greenAverage + blueAverage) / 3;
for (int y = 0; y < m_inputImage.Height; y++)
{
byte* inputCurrentRow = inputScan0 + y * inputStride;
byte* outputCurrentRow = outputScan0 + y * outputStride;
for (int x = 0; x < m_inputImage.Width; x++)
{
ColorBgr* inputColor = (ColorBgr*)(inputCurrentRow + x * bytesPerPixel);
ColorBgr* outputColor = (ColorBgr*)(outputCurrentRow + x * bytesPerPixel);
outputColor->R = (byte)(inputColor->R * average / redAverage);
outputColor->G = (byte)(inputColor->G * average / greenAverage);
outputColor->B = (byte)(inputColor->B * average / blueAverage);
}
}
m_inputImage.UnlockBits(inputBitmapData);
m_outputImage.UnlockBits(outputBitmapData);
}
private struct ColorBgr : IEquatable<ColorBgr>
{
public byte B;
public byte G;
public byte R;
public ColorBgr(byte b, byte g, byte r)
{
B = b;
G = g;
R = r;
}
public static bool operator ==(ColorBgr left, ColorBgr right)
{
return left.Equals(right);
}
public static bool operator !=(ColorBgr left, ColorBgr right)
{
return !(left == right);
}
public bool Equals(ColorBgr other)
{
return this.B == other.B && this.G == other.G && this.R == other.R;
}
public override bool Equals(object? obj)
{
if (obj is ColorBgr)
return Equals((ColorBgr)obj);
return false;
}
public override int GetHashCode()
{
return new byte[] { B, G, R }.GetHashCode();
}
}
}
}

GeoTIFF libtiff.net get elevation data in c#

I am trying to use libtiff.net to read elevation data from a GeoTIFF file.
So far I have mostly just been able to read metadata from the file using the example at libtiff.net's webpage.
But howto read elevation data I do not understand... I tried first reading with Tiff.ReadScanline() as described here but the file I have seems to be stored differently (probably in tiles if I understand it correctly)
Here is the metadata (as far as I have been able to read) (the tiff file is from the danish terrain elevation data set):
Tiff c:\Users***\DTM_1km_6170_500.tif, page 0 has following tags set:
IMAGEWIDTH System.Int32 : 2500
IMAGELENGTH System.Int32 : 2500
BITSPERSAMPLE System.Int16 : 32
COMPRESSION BitMiracle.LibTiff.Classic.Compression : ADOBE_DEFLATE
PHOTOMETRIC BitMiracle.LibTiff.Classic.Photometric : MINISBLACK
STRIPOFFSETS System.UInt64[] : System.UInt64[]
SAMPLESPERPIXEL System.Int16 : 1
STRIPBYTECOUNTS System.UInt64[] : System.UInt64[]
PLANARCONFIG BitMiracle.LibTiff.Classic.PlanarConfig : CONTIG
PREDICTOR BitMiracle.LibTiff.Classic.Predictor : FLOATINGPOINT
TILEWIDTH System.Int32 : 256
TILELENGTH System.Int32 : 256
TILEOFFSETS System.UInt64[] : System.UInt64[]
TILEBYTECOUNTS System.UInt64[] : System.UInt64[]
SAMPLEFORMAT BitMiracle.LibTiff.Classic.SampleFormat : IEEEFP
DATATYPE System.Int16 : 3
GEOTIFF_MODELPIXELSCALETAG System.Int32 : 3 GEOTIFF_MODELPIXELSCALETAG
System.Byte[] : Ù?Ù?
GEOTIFF_MODELTIEPOINTTAG System.Int32 : 6 GEOTIFF_MODELTIEPOINTTAG
System.Byte[] : A ^WA
34735 System.Int32 : 36 34735 System.Byte[] :
± ± #°  èd )#
34736 System.Int32 : 3 34736 System.Byte[] :
34737 System.Int32 : 30 34737 System.Byte[] : ETRS89 / UTM zone
32N|ETRS89|
42113 System.Int32 : 6 42113 System.Byte[] : -9999
The code I have written so far is as follows:
namespace GeoTIFFReader
{
public class GeoTIFF
{
private double[,] heightmap;
private double dx;
private double dy;
private double startx;
private double starty;
public GeoTIFF(string fn)
{
using (Tiff tiff = Tiff.Open(fn, "r"))
{
if (tiff == null)
{
// Error - could not open
return;
}
int width = tiff.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
int height = tiff.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
heightmap = new double[width, height];
FieldValue[] modelPixelScaleTag = tiff.GetField(TiffTag.GEOTIFF_MODELPIXELSCALETAG);
FieldValue[] modelTiePointTag = tiff.GetField(TiffTag.GEOTIFF_MODELTIEPOINTTAG);
byte[] modelPixelScale = modelPixelScaleTag[1].GetBytes();
dx = BitConverter.ToDouble(modelPixelScale, 0);
dy = BitConverter.ToDouble(modelPixelScale, 8) * -1;
byte[] modelTransformation = modelTiePointTag[1].GetBytes();
double originLon = BitConverter.ToDouble(modelTransformation, 24);
double originLat = BitConverter.ToDouble(modelTransformation, 32);
startx = originLon + dx / 2.0;
starty = originLat + dy / 2.0;
double curx = startx;
double cury = starty;
FieldValue[] bitsPerSampleTag = tiff.GetField(TiffTag.BITSPERSAMPLE);
FieldValue[] tilewtag = tiff.GetField(TiffTag.TILEWIDTH);
FieldValue[] tilehtag = tiff.GetField(TiffTag.TILELENGTH);
int tilew = tilewtag[0].ToInt();
int tileh = tilehtag[0].ToInt();
var tile = new byte[tilew*tileh];
//var scanline = new byte[tiff.ScanlineSize()]; Does not work... wrong format
for (int il = 0; il < height; il++)
{
//tiff.ReadScanline(scanline, il); // Load il'th line of data
for (int ir = 0; ir < width; ir++)
{
// Here I would like to read each pixel data that contains elevation in gray-scale in f32 as far I as I understand from metadata
//object value = scanline[ir];
//heightmap[ir, il] = double.Parse(value.ToString());
}
}
Console.WriteLine(heightmap.ToString());
}
}
}
}
So if anyone knows howto extract this data, that would be very much appreciated.
So I stumbled across some hinting that lead me to find an answer to the specific question..:
int tileSize = tiff.TileSize();
for (int iw = 0; iw < nWidth; iw += tilew)
{
for (int ih = 0; ih < nHeight; ih += tileh)
{
byte[] buffer = new byte[tileSize];
tiff.ReadTile(buffer, 0, iw, ih, 0, 0);
for (int itw = 0; itw < tilew; itw++)
{
int iwhm = ih + itw;
if (iwhm > nWidth - 1)
{
break;
}
for (int ith = 0; ith < tileh; ith++)
{
int iyhm = iw + ith;
if (iyhm > nHeight - 1)
{
break;
}
heightMap[iwhm, iyhm] =
BitConverter.ToSingle(buffer, (itw * tileh + ith) * 4);
}
}
}
}
EDIT 2018-09-20:
#Graviton - Sorry for the long response time.. but here is the complete class I have used with a constructor that takes the filename as input and populates the heightMap (but #Nazonokaizijin looks a bit nicer and slimmer):
using System;
using BitMiracle.LibTiff.Classic;
using System.IO;
namespace GeoTIFFReader
{
public class GeoTIFF
{
private float[,] heightMap;
public float[,] HeightMap
{
get { return heightMap; }
private set { heightMap = value; }
}
private int nWidth;
public int NWidth
{
get { return nWidth; }
private set { nWidth = value; }
}
private int nHeight;
public int NHeight
{
get { return nHeight; }
private set { nHeight = value; }
}
private double dW;
public double DW
{
get { return dW; }
private set { dW = value; }
}
private double dH;
public double DH
{
get { return dH; }
private set { dH = value; }
}
private double startW;
public double StartW
{
get { return startW; }
private set { startW = value; }
}
private double startH;
public double StartH
{
get { return startH; }
private set { startH = value; }
}
public GeoTIFF(string fn)
{
using (Tiff tiff = Tiff.Open(fn, "r"))
{
if (tiff == null)
{
// Error - could not open
return;
}
nWidth = tiff.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
nHeight = tiff.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
heightMap = new float[nWidth, nHeight];
FieldValue[] modelPixelScaleTag = tiff.GetField(TiffTag.GEOTIFF_MODELPIXELSCALETAG);
FieldValue[] modelTiePointTag = tiff.GetField(TiffTag.GEOTIFF_MODELTIEPOINTTAG);
byte[] modelPixelScale = modelPixelScaleTag[1].GetBytes();
dW = BitConverter.ToDouble(modelPixelScale, 0);
dH = BitConverter.ToDouble(modelPixelScale, 8) * -1;
byte[] modelTransformation = modelTiePointTag[1].GetBytes();
double originLon = BitConverter.ToDouble(modelTransformation, 24);
double originLat = BitConverter.ToDouble(modelTransformation, 32);
startW = originLon + dW / 2.0;
startH = originLat + dH / 2.0;
FieldValue[] tileByteCountsTag = tiff.GetField(TiffTag.TILEBYTECOUNTS);
long[] tileByteCounts = tileByteCountsTag[0].TolongArray();
FieldValue[] bitsPerSampleTag = tiff.GetField(TiffTag.BITSPERSAMPLE);
int bytesPerSample = bitsPerSampleTag[0].ToInt() / 8;
FieldValue[] tilewtag = tiff.GetField(TiffTag.TILEWIDTH);
FieldValue[] tilehtag = tiff.GetField(TiffTag.TILELENGTH);
int tilew = tilewtag[0].ToInt();
int tileh = tilehtag[0].ToInt();
int tileWidthCount = nWidth / tilew;
int remainingWidth = nWidth - tileWidthCount * tilew;
if (remainingWidth > 0)
{
tileWidthCount++;
}
int tileHeightCount = nHeight / tileh;
int remainingHeight = nHeight - tileHeightCount * tileh;
if (remainingHeight > 0)
{
tileHeightCount++;
}
int tileSize = tiff.TileSize();
for (int iw = 0; iw < nWidth; iw += tilew)
{
for (int ih = 0; ih < nHeight; ih += tileh)
{
byte[] buffer = new byte[tileSize];
tiff.ReadTile(buffer, 0, iw, ih, 0, 0);
for (int itw = 0; itw < tilew; itw++)
{
int iwhm = ih + itw;
if (iwhm > nWidth - 1)
{
break;
}
for (int ith = 0; ith < tileh; ith++)
{
int iyhm = iw + ith;
if (iyhm > nHeight - 1)
{
break;
}
heightMap[iwhm, iyhm] =
BitConverter.ToSingle(buffer, (itw * tileh + ith) * 4);
}
}
}
}
}
}
}
}
You can do it something like this:
private void ReadTiff()
{
Tiff tiff = Tiff.Open("myfile.tif", "r");
if (tiff == null)
return;
//Get the image size
int imageWidth = tiff.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
int imageHeight = tiff.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
//Get the tile size
int tileWidth = tiff.GetField(TiffTag.TILEWIDTH)[0].ToInt();
int tileHeight = tiff.GetField(TiffTag.TILELENGTH)[0].ToInt();
int tileSize = tiff.TileSize();
//Pixel depth
int depth = tileSize / (tileWidth * tileHeight);
byte[] buffer = new byte[tileSize];
for (int y = 0; y < imageHeight; y += tileHeight)
{
for (int x = 0; x < imageWidth; x += tileWidth)
{
//Read the value and store to the buffer
tiff.ReadTile(buffer, 0, x, y, 0, 0);
for (int kx = 0; kx < tileWidth; kx++)
{
for (int ky = 0; ky < tileHeight; ky++)
{
//Calculate the index in the buffer
int startIndex = (kx + tileWidth * ky) * depth;
if (startIndex >= buffer.Length)
continue;
//Calculate pixel index
int pixelX = x + kx;
int pixelY = y + ky;
if (pixelX >= imageWidth || pixelY >= imageHeight)
continue;
//Get the value for the target pixel
double value = BitConverter.ToSingle(buffer, startIndex);
}
}
}
}
tiff.Close();
}
It has a lib (in c#) complementary to LibTiff that makes the elevation query given a latitude/longitude, available at GeoTiffCOG:
GeoTiff geoTiff = new GeoTiff(file_tiff);
double value = geoTiff.GetElevationAtLatLon(latitude, longitude);
It also supports Cloud Optimized GeoTiff (COG) by adding URI as an argument.
I believe the problem is the PREDICTOR. Instead of placing the data in the file, it is LZW encoding of the differences in the data, with FLOATINGPOINT predictor. This was proprietary with Adobe. I have been searching for code do decrypt PREDICTOR_FLOATINGPOINT, myself. I think this github link
may have library code that will read and decode it.

Show each channel in WaveViever - Naudio

How can I draw only one channel. My example work for mono, but sound is stereo. I want here show only left or only right channel.
protected override void OnPaint(PaintEventArgs e)
{
if (waveStream != null)
{
waveStream.Position = 0;
int bytesRead;
byte[] waveData = new byte[samplesPerPixel * bytesPerSample];
waveStream.Position = startPosition + (e.ClipRectangle.Left * bytesPerSample * samplesPerPixel);
using (Pen linePen = new Pen(PenColor, PenWidth))
{
for (float x = e.ClipRectangle.X; x < e.ClipRectangle.Right; x += 1)
{
short low = 0;
short high = 0;
bytesRead = waveStream.Read(waveData, 0, samplesPerPixel * bytesPerSample);
if (bytesRead == 0)
break;
for (int n = 0; n < bytesRead; n+=2)
{
short sample = BitConverter.ToInt16(waveData, n);
if (sample < low) low = sample;
if (sample > high) high = sample;
}
float lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue);
float highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue);
e.Graphics.DrawLine(linePen, x, this.Height * lowPercent, x, this.Height * highPercent);
}
}
}
base.OnPaint(e);
}
I read, 1-3-5... for first channel, and 2-4-6... for second channel. But if in for loop change this, I got different graph, but not correct. How can I fix that?
Use WaveWiever Painter. Work real-time and for two channels.

Nonspecific exception when running unsafe code to process images

I'm doing some image processing and ran in to an exception.
Let me explain the logic process;
Resize the image to to a smaller size
Turn it grayscale
Threshold the image
Save it for use later on.
When you threshold the image, the constructor can take an int that sets the intensity of the filter. The best way I've found to get this "magic number" is to use a method called GetOtsuThreshold. It uses unsafe code but works well. However, something strange happens when you call that method. After you call the otsu method, it causes the Aforge...Threshold.ApplyInPlace() method to throw a Parameter is not valid exception. If you don't call it (When the code is commented out) the whole thing runs just fine though.
Wot's the deal?
EDIT: Found the problem; You must put a new a new image into the otsu method because it disposes of the image!!
using System;
using System.Drawing;
using System.Drawing.Imaging;
using AForge.Imaging.Filters;
namespace Puma.Ocr.Tests
{
class FormatImage
{
public static Bitmap _FullImageOfCoin;
public FormatImage(string path)
{
_FullImageOfCoin = ScaleImage(new Bitmap(path), 2000, 2000);
GrayscaleImage();
ThresholdImage();
}
private void GrayscaleImage()
{
Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
// apply the filter
_FullImageOfCoin = filter.Apply(_FullImageOfCoin);
}
private void ThresholdImage()
{
//Causes the exception
Threshold threshold = new Threshold(getOtsuThreshold(_FullImageOfCoin));
//Runs fine
//Threshold threshold = new Threshold();
threshold.ApplyInPlace(_FullImageOfCoin);
_FullImageOfCoin.Save(#"C:\users\school\desktop\thresholded.bmp");
}
public static Bitmap ScaleImage(Bitmap image, int maxWidth, int maxHeight)
{
var ratioX = (double)maxWidth / image.Width;
var ratioY = (double)maxHeight / image.Height;
var ratio = Math.Min(ratioX, ratioY);
var newWidth = (int)(image.Width * ratio);
var newHeight = (int)(image.Height * ratio);
var newImage = new Bitmap(newWidth, newHeight);
Graphics.FromImage(newImage).DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
public int getOtsuThreshold(Bitmap bmp)
{
byte t = 0;
float[] vet = new float[256];
int[] hist = new int[256];
vet.Initialize();
float p1, p2, p12;
int k;
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* p = (byte*)(void*)bmData.Scan0.ToPointer();
getHistogram(p, bmp.Width, bmp.Height, bmData.Stride, hist);
for (k = 1; k != 255; k++)
{
p1 = Px(0, k, hist);
p2 = Px(k + 1, 255, hist);
p12 = p1 * p2;
if (p12 == 0)
p12 = 1;
float diff = (Mx(0, k, hist) * p2) - (Mx(k + 1, 255, hist) * p1);
vet[k] = (float)diff * diff / p12;
}
}
bmp.UnlockBits(bmData);
t = (byte)findMax(vet, 256);
bmp.Dispose();
return t;
}
private unsafe void getHistogram(byte* p, int w, int h, int ws, int[] hist)
{
hist.Initialize();
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w * 3; j += 3)
{
int index = i * ws + j;
hist[p[index]]++;
}
}
}
private int findMax(float[] vec, int n)
{
float maxVec = 0;
int idx = 0;
int i;
for (i = 1; i <= n - 1; i++)
{
if (vec[i] > maxVec)
{
maxVec = vec[i];
idx = i;
}
}
return idx;
}
private float Px(int init, int end, int[] hist)
{
int sum = 0;
int i;
for (i = init; i <= end; i++)
sum += hist[i];
return (float)sum;
}
// function is used to compute the mean values in the equation (mu)
private float Mx(int init, int end, int[] hist)
{
int sum = 0;
int i;
for (i = init; i <= end; i++)
sum += i * hist[i];
return (float)sum;
}
}
}
A few ideas:
Use debugging and follow this method step by step to see if it is well working
Your getOtsuThreshold(Bitmap bmp) is returning an int, but the variable t returned is a byte: try to cast the value?
If the int returned by getOtsuThreshold is okay, check that the value is in the good range provided the API info (http://www.aforgenet.com/framework/docs/html/503a43b9-d98b-a19f-b74e-44767916ad65.htm):
Since the filter can be applied as to 8 bpp and to 16 bpp images, the
ThresholdValue value should be set appropriately to the pixel format.
In the case of 8 bpp images the threshold value is in the [0, 255]
range, but in the case of 16 bpp images the threshold value is in the
[0, 65535] range.
Alright, found the answer. By putting the _Fullimage of coin into the otsu method it stripped the variable of all it properties. I don't know how, but by putting a new Bitmap into the otsu method it fixed the problem.

Open source C# code to present wave form?

Is there any open source C# code or library to present a graphical waveform given a byte array?
This is as open source as it gets:
public static void DrawNormalizedAudio(ref float[] data, PictureBox pb,
Color color)
{
Bitmap bmp;
if (pb.Image == null)
{
bmp = new Bitmap(pb.Width, pb.Height);
}
else
{
bmp = (Bitmap)pb.Image;
}
int BORDER_WIDTH = 5;
int width = bmp.Width - (2 * BORDER_WIDTH);
int height = bmp.Height - (2 * BORDER_WIDTH);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Black);
Pen pen = new Pen(color);
int size = data.Length;
for (int iPixel = 0; iPixel < width; iPixel++)
{
// determine start and end points within WAV
int start = (int)((float)iPixel * ((float)size / (float)width));
int end = (int)((float)(iPixel + 1) * ((float)size / (float)width));
float min = float.MaxValue;
float max = float.MinValue;
for (int i = start; i < end; i++)
{
float val = data[i];
min = val < min ? val : min;
max = val > max ? val : max;
}
int yMax = BORDER_WIDTH + height - (int)((max + 1) * .5 * height);
int yMin = BORDER_WIDTH + height - (int)((min + 1) * .5 * height);
g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax,
iPixel + BORDER_WIDTH, yMin);
}
}
pb.Image = bmp;
}
This function will produce something like this:
This takes an array of samples in floating-point format (where all sample values range from -1 to +1). If your original data is actually in the form of a byte[] array, you'll have to do a little bit of work to convert it to float[]. Let me know if you need that, too.
Update: since the question technically asked for something to render a byte array, here are a couple of helper methods:
public float[] FloatArrayFromStream(System.IO.MemoryStream stream)
{
return FloatArrayFromByteArray(stream.GetBuffer());
}
public float[] FloatArrayFromByteArray(byte[] input)
{
float[] output = new float[input.Length / 4];
for (int i = 0; i < output.Length; i++)
{
output[i] = BitConverter.ToSingle(input, i * 4);
}
return output;
}
Update 2: I forgot there's a better way to do this:
public float[] FloatArrayFromByteArray(byte[] input)
{
float[] output = new float[input.Length / 4];
Buffer.BlockCopy(input, 0, output, 0, input.Length);
return output;
}
I'm just so in love with for loops, I guess.
I modified MusiGenesis's solution a little bit.
This gave me a much better result, especially with house music :)
public static Bitmap DrawNormalizedAudio(List<float> data, Color foreColor, Color backColor, Size imageSize)
{
Bitmap bmp = new Bitmap(imageSize.Width, imageSize.Height);
int BORDER_WIDTH = 0;
float width = bmp.Width - (2 * BORDER_WIDTH);
float height = bmp.Height - (2 * BORDER_WIDTH);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(backColor);
Pen pen = new Pen(foreColor);
float size = data.Count;
for (float iPixel = 0; iPixel < width; iPixel += 1)
{
// determine start and end points within WAV
int start = (int)(iPixel * (size / width));
int end = (int)((iPixel + 1) * (size / width));
if (end > data.Count)
end = data.Count;
float posAvg, negAvg;
averages(data, start, end, out posAvg, out negAvg);
float yMax = BORDER_WIDTH + height - ((posAvg + 1) * .5f * height);
float yMin = BORDER_WIDTH + height - ((negAvg + 1) * .5f * height);
g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax, iPixel + BORDER_WIDTH, yMin);
}
}
return bmp;
}
private static void averages(List<float> data, int startIndex, int endIndex, out float posAvg, out float negAvg)
{
posAvg = 0.0f;
negAvg = 0.0f;
int posCount = 0, negCount = 0;
for (int i = startIndex; i < endIndex; i++)
{
if (data[i] > 0)
{
posCount++;
posAvg += data[i];
}
else
{
negCount++;
negAvg += data[i];
}
}
posAvg /= posCount;
negAvg /= negCount;
}
with adapted code from robby and using Graphics.Fill/DrawClosedCurve with antialiasing, I get a pretty good looking result.
here's the code:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace Soundfingerprinting.Audio.Services
{
public static class AudioVisualizationService
{
public class WaveVisualizationConfiguration
{
public Nullable<Color> AreaColor { get; set; }
public Nullable<Color> EdgeColor { get; set; }
public int EdgeSize { get; set; }
public Nullable<Rectangle> Bounds { get; set; }
public double Overlap { get; set; }
public int Step { get; set; }
}
public static void DrawWave(float[] data, Bitmap bitmap, WaveVisualizationConfiguration config = null)
{
Color areaColor = Color.FromArgb(0x7F87CEFA);// Color.LightSkyBlue; semi transparent
Color edgeColor = Color.DarkSlateBlue;
int edgeSize = 2;
int step = 2;
double overlap = 0.10f; // would better use a windowing function
Rectangle bounds = Rectangle.FromLTRB(0, 0, bitmap.Width, bitmap.Height);
if (config != null)
{
edgeSize = config.EdgeSize;
if (config.AreaColor.HasValue)
areaColor = config.AreaColor.GetValueOrDefault();
if (config.EdgeColor.HasValue)
edgeColor = config.EdgeColor.GetValueOrDefault();
if (config.Bounds.HasValue)
bounds = config.Bounds.GetValueOrDefault();
step = Math.Max(1, config.Step);
overlap = config.Overlap;
}
float width = bounds.Width;
float height = bounds.Height;
using (Graphics g = Graphics.FromImage(bitmap))
{
Pen edgePen = new Pen(edgeColor);
edgePen.LineJoin = LineJoin.Round;
edgePen.Width = edgeSize;
Brush areaBrush = new SolidBrush(areaColor);
float size = data.Length;
PointF[] topCurve = new PointF[(int)width / step];
PointF[] bottomCurve = new PointF[(int)width / step];
int idx = 0;
for (float iPixel = 0; iPixel < width; iPixel += step)
{
// determine start and end points within WAV
int start = (int)(iPixel * (size / width));
int end = (int)((iPixel + step) * (size / width));
int window = end - start;
start -= (int)(overlap * window);
end += (int)(overlap * window);
if (start < 0)
start = 0;
if (end > data.Length)
end = data.Length;
float posAvg, negAvg;
averages(data, start, end, out posAvg, out negAvg);
float yMax = height - ((posAvg + 1) * .5f * height);
float yMin = height - ((negAvg + 1) * .5f * height);
float xPos = iPixel + bounds.Left;
if (idx >= topCurve.Length)
idx = topCurve.Length - 1;
topCurve[idx] = new PointF(xPos, yMax);
bottomCurve[bottomCurve.Length - idx - 1] = new PointF(xPos, yMin);
idx++;
}
PointF[] curve = new PointF[topCurve.Length * 2];
Array.Copy(topCurve, curve, topCurve.Length);
Array.Copy(bottomCurve, 0, curve, topCurve.Length, bottomCurve.Length);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillClosedCurve(areaBrush, curve, FillMode.Winding, 0.15f);
if (edgeSize > 0)
g.DrawClosedCurve(edgePen, curve, 0.15f, FillMode.Winding);
}
}
private static void averages(float[] data, int startIndex, int endIndex, out float posAvg, out float negAvg)
{
posAvg = 0.0f;
negAvg = 0.0f;
int posCount = 0, negCount = 0;
for (int i = startIndex; i < endIndex; i++)
{
if (data[i] > 0)
{
posCount++;
posAvg += data[i];
}
else
{
negCount++;
negAvg += data[i];
}
}
if (posCount > 0)
posAvg /= posCount;
if (negCount > 0)
negAvg /= negCount;
}
}
}
In NAudio, there is code to draw audio waveforms in both WinForms and WPF. Have a look at the demo projects for examples of how to use it.
I've been a fan of ZedGraph for many years and have used it to display all kinds of data in various projects.
The following sample code graphs an array of doubles varying between -1 and 1:
void DisplayWaveGraph(ZedGraphControl graphControl, double[] waveData)
{
var pane = graphControl.GraphPane;
pane.Chart.Border.IsVisible = false;
pane.Chart.Fill.IsVisible = false;
pane.Fill.Color = Color.Black;
pane.Margin.All = 0;
pane.Title.IsVisible = false;
pane.XAxis.IsVisible = false;
pane.XAxis.Scale.Max = waveData.Length - 1;
pane.XAxis.Scale.Min = 0;
pane.YAxis.IsVisible = false;
pane.YAxis.Scale.Max = 1;
pane.YAxis.Scale.Min = -1;
var timeData = Enumerable.Range(0, waveData.Length)
.Select(i => (double) i)
.ToArray();
pane.AddCurve(null, timeData, waveData, Color.Lime, SymbolType.None);
graphControl.AxisChange();
}
The above sample mimics the style of an audio editor by suppressing the axes and changing the colors to produce the following:

Categories

Resources