/// <summary> /// This takes an original image and calculates the header content for all the lower resolution tiles. /// This does not actually write the bytes for those images. /// </summary> /// <param name="numRows">The number of rows in the original image</param> /// <param name="numColumns">The number of columns in the original image</param> /// <param name="affineCoefficients"> /// the array of doubles in ABCDEF order /// X' = A + Bx + Cy /// Y' = D + Ex + Fy /// </param> public void CreateHeaders(int numRows, int numColumns, double[] affineCoefficients) { Header = new PyramidHeader(); List <PyramidImageHeader> headers = new List <PyramidImageHeader>(); int scale = 0; long offset = 0; int nr = numRows; int nc = numColumns; while (nr > 2 && nc > 2) { PyramidImageHeader ph = new PyramidImageHeader(); ph.SetAffine(affineCoefficients, scale); ph.SetNumRows(numRows, scale); ph.SetNumColumns(numColumns, scale); ph.Offset = offset; offset += ph.NumRows * ph.NumColumns * 4; nr = nr / 2; nc = nc / 2; scale++; headers.Add(ph); } Header.ImageHeaders = headers.ToArray(); }
/// <summary> /// This assumes that the base image has been written to the file. This will now attempt to calculate /// the down-sampled images. /// </summary> public void CreatePyramids2() { double count = Header.ImageHeaders[0].NumRows; ProgressMeter pm = new ProgressMeter(ProgressHandler, "Generating Pyramids", count); int prog = 0; for (int scale = 0; scale < Header.ImageHeaders.Length - 1; scale++) { PyramidImageHeader ph = Header.ImageHeaders[scale]; int rows = ph.NumRows; int cols = ph.NumColumns; // Horizontal Blur Pass byte[] r1 = ReadWindow(0, 0, 1, cols, scale); byte[] r2 = ReadWindow(1, 0, 1, cols, scale); byte[] vals = Blur(null, r1, r2); vals = DownSample(vals); WriteWindow(vals, 0, 0, 1, cols / 2, scale + 1); prog++; pm.CurrentValue = prog; byte[] r3 = ReadWindow(2, 0, 1, cols, scale); vals = Blur(r1, r2, r3); vals = DownSample(vals); WriteWindow(vals, 1, 0, 1, cols / 2, scale + 1); prog++; pm.CurrentValue = prog; for (int row = 3; row < rows - 1; row++) { r1 = r2; r2 = r3; r3 = ReadWindow(row, 0, 1, cols, scale); prog++; pm.CurrentValue = prog; if (row % 2 == 1) { continue; } vals = Blur(r1, r2, r3); vals = DownSample(vals); WriteWindow(vals, (row / 2) - 1, 0, 1, cols / 2, scale + 1); } if ((rows - 1) % 2 == 0) { vals = Blur(r2, r3, r2); vals = DownSample(vals); WriteWindow(vals, (rows / 2) - 1, 0, 1, cols / 2, scale + 1); } prog++; pm.CurrentValue = prog; } pm.Reset(); }
/// <summary> /// For big images this won't work, but this gets the 0 scale original image as a bitmap. /// </summary> /// <returns>The original image as bitmap.</returns> public override Bitmap GetBitmap() { PyramidImageHeader ph = Header.ImageHeaders[0]; Rectangle bnds = new Rectangle(0, 0, ph.NumColumns, ph.NumRows); byte[] data = ReadWindow(0, 0, ph.NumRows, ph.NumColumns, 0); Bitmap bmp = new Bitmap(ph.NumColumns, ph.NumRows); BitmapData bData = bmp.LockBits(bnds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); Marshal.Copy(data, 0, bData.Scan0, data.Length); bmp.UnlockBits(bData); return(bmp); }
/// <summary> /// Reads the header only from the specified mwi file. The header is in xml format. /// This is a test. We may have to jurry rig the thing to ensure it ignores the actual /// image content. /// </summary> /// <param name="fileName">Whether this is the mwi or mwh file, this reads the mwh file for the fileName.</param> public void ReadHeader(string fileName) { string header = Path.ChangeExtension(fileName, "mwh"); XmlSerializer s = new XmlSerializer(typeof(PyramidHeader)); TextReader r = new StreamReader(header); _header = (PyramidHeader)s.Deserialize(r); PyramidImageHeader ph = _header.ImageHeaders[0]; Bounds = new RasterBounds(ph.NumRows, ph.NumColumns, ph.Affine); Width = _header.ImageHeaders[0].NumColumns; Height = _header.ImageHeaders[0].NumRows; r.Close(); }
/// <summary> /// This writes a window of byte values (ARGB order) to the file. This assumes that the headers already exist. /// If the headers have not been created or the bounds extend beyond the header numRows and numColumns for the /// specified scale, this will throw an exception. /// </summary> /// <param name="startRow">The integer start row</param> /// <param name="startColumn">The integer start column</param> /// <param name="numRows">The integer number of rows in the window</param> /// <param name="numColumns">The integer number of columns in the window</param> /// <param name="scale">The integer scale. 0 is the original image.</param> /// <returns>The bytes created by this process</returns> /// <exception cref="PyramidUndefinedHeaderException">Occurs when attempting to write data before the headers are defined.</exception> /// <exception cref="PyramidOutOfBoundsException">Occurs if the range specified is outside the bounds for the specified image scale.</exception> public byte[] ReadWindow(int startRow, int startColumn, int numRows, int numColumns, int scale) { byte[] bytes = new byte[numRows * numColumns * 4]; if (Header == null || Header.ImageHeaders.Length <= scale || Header.ImageHeaders[scale] == null) { throw new PyramidUndefinedHeaderException(); } PyramidImageHeader ph = Header.ImageHeaders[scale]; if (startRow < 0 || startColumn < 0 || numRows + startRow > ph.NumRows || numColumns + startColumn > ph.NumColumns) { throw new PyramidOutOfBoundsException(); } if (startColumn == 0 && numColumns == ph.NumColumns) { // write all in one pass. FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read); fs.Seek(ph.Offset, SeekOrigin.Begin); fs.Seek((startRow * ph.NumColumns) * 4, SeekOrigin.Current); fs.Read(bytes, 0, bytes.Length); fs.Close(); } else { // write all in one pass. FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read); fs.Seek(ph.Offset, SeekOrigin.Begin); fs.Seek((startRow * ph.NumColumns) * 4, SeekOrigin.Current); int before = startColumn * 4; int after = (ph.NumColumns - (startColumn + numColumns)) * 4; for (int row = startRow; row < startRow + numRows; row++) { fs.Seek(before, SeekOrigin.Current); fs.Read(bytes, (row - startRow) * numColumns * 4, numColumns * 4); fs.Seek(after, SeekOrigin.Current); } fs.Close(); } return(bytes); }
/// <summary> /// This writes a window of byte values (ARGB order) to the file. This assumes that the headers already exist. /// If the headers have not been created or the bounds extend beyond the header numRows and numColumns for the /// specified scale, this will throw an exception. /// </summary> /// <param name="bytes">The byte array.</param> /// <param name="startRow">The integer start row.</param> /// <param name="startColumn">The integer start column.</param> /// <param name="numRows">The integer number of rows in the window.</param> /// <param name="numColumns">The integer number of columns in the window.</param> /// <param name="scale">The integer scale. 0 is the original image.</param> /// <param name="pm">The progress meter to advance by row. Calls Next() for each row.</param> /// <exception cref="PyramidUndefinedHeaderException">Occurs when attempting to write data before the headers are defined.</exception> /// <exception cref="PyramidOutOfBoundsException">Occurs if the range specified is outside the bounds for the specified image scale.</exception> public void WriteWindow(byte[] bytes, int startRow, int startColumn, int numRows, int numColumns, int scale, ProgressMeter pm) { if (Header == null || Header.ImageHeaders.Length <= scale || Header.ImageHeaders[scale] == null) { throw new PyramidUndefinedHeaderException(); } PyramidImageHeader ph = Header.ImageHeaders[scale]; if (startRow < 0 || startColumn < 0 || numRows + startRow > ph.NumRows || numColumns + startColumn > ph.NumColumns) { throw new PyramidOutOfBoundsException(); } if (startColumn == 0 && numColumns == ph.NumColumns) { // write all in one pass. FileStream fs = new(Filename, FileMode.OpenOrCreate, FileAccess.Write); fs.Seek(ph.Offset, SeekOrigin.Begin); fs.Seek((startRow * ph.NumColumns) * 4, SeekOrigin.Current); fs.Write(bytes, 0, bytes.Length); fs.Close(); } else { // write one row at a time FileStream fs = new(Filename, FileMode.Open, FileAccess.Write); fs.Seek(ph.Offset, SeekOrigin.Begin); fs.Seek((startRow * ph.NumColumns) * 4, SeekOrigin.Current); int before = startColumn * 4; int after = (ph.NumColumns - (startColumn + numColumns)) * 4; for (int row = startRow; row < startRow + numRows; row++) { fs.Seek(before, SeekOrigin.Current); fs.Write(bytes, (row - startRow) * numColumns * 4, numColumns * 4); fs.Seek(after, SeekOrigin.Current); pm.Next(); } fs.Write(bytes, 0, bytes.Length); fs.Close(); } }
/// <summary> /// For big images the scale that is just one step larger than the specified window will be used. /// </summary> /// <param name="envelope">The envelope containing the geographic extent.</param> /// <param name="window">The rectangle containing the window extent.</param> /// <returns>The bitmap.</returns> public override Bitmap GetBitmap(Extent envelope, Rectangle window) { if (window.Width == 0 || window.Height == 0) { return(null); } if (Header?.ImageHeaders?[0] == null) { return(null); } Rectangle expWindow = window.ExpandBy(1); Envelope expEnvelope = envelope.ToEnvelope().Reproportion(window, expWindow); Envelope env = expEnvelope.Intersection(Bounds.Extent.ToEnvelope()); if (env == null || env.IsNull || env.Height == 0 || env.Width == 0) { return(null); } PyramidImageHeader he = Header.ImageHeaders[0]; int scale; double cwa = expWindow.Width / expEnvelope.Width; double cha = expWindow.Height / expEnvelope.Height; for (scale = 0; scale < Header.ImageHeaders.Length; scale++) { PyramidImageHeader ph = Header.ImageHeaders[scale]; if (cwa > ph.NumColumns / Bounds.Width || cha > ph.NumRows / Bounds.Height) { if (scale > 0) { scale -= 1; } break; } he = ph; } RasterBounds overviewBounds = new RasterBounds(he.NumRows, he.NumColumns, he.Affine); Rectangle r = overviewBounds.CellsContainingExtent(envelope); if (r.Width == 0 || r.Height == 0) { return(null); } byte[] vals = ReadWindow(r.Y, r.X, r.Height, r.Width, scale); Bitmap bmp = new Bitmap(r.Width, r.Height); BitmapData bData = bmp.LockBits(new Rectangle(0, 0, r.Width, r.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); Marshal.Copy(vals, 0, bData.Scan0, vals.Length); bmp.UnlockBits(bData); // Use the cell coordinates to determine the affine coefficients for the cells retrieved. double[] affine = new double[6]; Array.Copy(he.Affine, affine, 6); affine[0] = affine[0] + (r.X * affine[1]) + (r.Y * affine[2]); affine[3] = affine[3] + (r.X * affine[4]) + (r.Y * affine[5]); if (window.Width == 0 || window.Height == 0) { return(null); } Bitmap result = new Bitmap(window.Width, window.Height); Graphics g = Graphics.FromImage(result); // Gets the scaling factor for converting from geographic to pixel coordinates double dx = window.Width / envelope.Width; double dy = window.Height / envelope.Height; double[] a = affine; // gets the affine scaling factors. float m11 = Convert.ToSingle(a[1] * dx); float m22 = Convert.ToSingle(a[5] * -dy); float m21 = Convert.ToSingle(a[2] * dx); float m12 = Convert.ToSingle(a[4] * -dy); double l = a[0] - (.5 * (a[1] + a[2])); // Left of top left pixel double t = a[3] - (.5 * (a[4] + a[5])); // top of top left pixel float xShift = (float)((l - envelope.MinX) * dx); float yShift = (float)((envelope.MaxY - t) * dy); g.Transform = new Matrix(m11, m12, m21, m22, xShift, yShift); g.PixelOffsetMode = PixelOffsetMode.Half; if (m11 > 1 || m22 > 1) { g.InterpolationMode = InterpolationMode.NearestNeighbor; } g.DrawImage(bmp, new PointF(0, 0)); bmp.Dispose(); g.Dispose(); return(result); }
/// <summary> /// This takes an original image and calculates the header content for all the lower resolution tiles. /// This does not actually write the bytes for those images. /// </summary> /// <param name="numRows">The number of rows in the original image</param> /// <param name="numColumns">The number of columns in the original image</param> /// <param name="affineCoefficients"> /// the array of doubles in ABCDEF order /// X' = A + Bx + Cy /// Y' = D + Ex + Fy /// </param> public void CreateHeaders(int numRows, int numColumns, double[] affineCoefficients) { _header = new PyramidHeader(); List<PyramidImageHeader> headers = new List<PyramidImageHeader>(); int scale = 0; long offset = 0; int nr = numRows; int nc = numColumns; while (nr > 2 && nc > 2) { PyramidImageHeader ph = new PyramidImageHeader(); ph.SetAffine(affineCoefficients, scale); ph.SetNumRows(numRows, scale); ph.SetNumColumns(numColumns, scale); ph.Offset = offset; offset += (ph.NumRows * ph.NumColumns * 4); nr = nr / 2; nc = nc / 2; scale++; headers.Add(ph); } _header.ImageHeaders = headers.ToArray(); }