protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); m_MousePositionCurrent = e.Location; if (m_MouseButtonsDown == MouseButtons.None) { m_ButtonDownMousePosition = e.Location; } switch (m_ManipulationState) { case MANIPULATION_STATE.PICK_COLOR: Cursor = Cursors.Cross; ImageUtility.float2 UV0 = Client2ImageUV(m_ButtonDownMousePosition); ImageUtility.float2 UV1 = Client2ImageUV(e.Location); m_ColorPickingUpdateDelegate(UV0, UV1); Invalidate(); break; default: Cursor = Cursors.Default; break; } }
/// <summary> /// Sets the crop rectangle to a specific value /// </summary> public void SetCropRectangle(ImageUtility.float2 _Center, ImageUtility.float2 _HalfSize, float _Rotation) { m_CropRectangleIsDefault = false; m_CropRectangleCenter = _Center; m_CropRectangleHalfSize = _HalfSize; m_CropRectangleRotation = _Rotation; Invalidate(); }
private PointF ImageUV2Client(ImageUtility.float2 _Position) { RectangleF ImageRect = ImageClientRect(); // return new PointF( ImageRect.X + _Position.x * ImageRect.Width, ImageRect.Y + _Position.y * ImageRect.Height ); // return new PointF( ImageRect.X + _Position.x * ImageRect.Height, ImageRect.Y + _Position.y * ImageRect.Height ); return(new PointF(0.5f * (Width - ImageRect.Height) + _Position.x * ImageRect.Height, ImageRect.Y + _Position.y * ImageRect.Height)); }
protected ImageUtility.float2[] BuildClipVertices(ImageUtility.float2 _Center, ImageUtility.float2 _HalfSize, ImageUtility.float2 _AxisX, ImageUtility.float2 _AxisY) { return(new ImageUtility.float2[4] { _Center - _HalfSize.x * _AxisX + _HalfSize.y * _AxisY, _Center + _HalfSize.x * _AxisX + _HalfSize.y * _AxisY, _Center - _HalfSize.x * _AxisX - _HalfSize.y * _AxisY, _Center + _HalfSize.x * _AxisX - _HalfSize.y * _AxisY, }); }
/// <summary> /// Resets the crop rectangle to the entire image /// </summary> public void ResetCropRectangle() { m_CropRectangleIsDefault = true; if (m_Image == null) { return; } m_CropRectangleCenter = new ImageUtility.float2(0.5f, 0.5f); m_CropRectangleHalfSize = new ImageUtility.float2(0.5f * ImageAspectRatio, 0.5f); m_CropRectangleRotation = 0.0f; Invalidate(); }
private PointF[] m_ClientCropRectangleVertices = new PointF[4]; // Top-left, top-right, bottom-left, bottom-right private void UpdateCropRectangleVertices() { m_CropRectangleAxisX = new ImageUtility.float2((float)Math.Cos(m_CropRectangleRotation), -(float)Math.Sin(m_CropRectangleRotation)); m_CropRectangleAxisY = new ImageUtility.float2(-(float)Math.Sin(m_CropRectangleRotation), -(float)Math.Cos(m_CropRectangleRotation)); RectangleF ImageRect = ImageClientRect(); m_ClientCropRectangleCenter = ImageUV2Client(m_CropRectangleCenter); ImageUtility.float2[] Vertices = new ImageUtility.float2[4] { m_CropRectangleCenter - m_CropRectangleHalfSize.x * m_CropRectangleAxisX + m_CropRectangleHalfSize.y * m_CropRectangleAxisY, m_CropRectangleCenter + m_CropRectangleHalfSize.x * m_CropRectangleAxisX + m_CropRectangleHalfSize.y * m_CropRectangleAxisY, m_CropRectangleCenter - m_CropRectangleHalfSize.x * m_CropRectangleAxisX - m_CropRectangleHalfSize.y * m_CropRectangleAxisY, m_CropRectangleCenter + m_CropRectangleHalfSize.x * m_CropRectangleAxisX - m_CropRectangleHalfSize.y * m_CropRectangleAxisY, }; for (int i = 0; i < 4; i++) { m_ClientCropRectangleVertices[i] = ImageUV2Client(Vertices[i]); } }
protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); m_MouseButtonsDown &= ~e.Button; // End manipulation switch (m_ManipulationState) { case MANIPULATION_STATE.PICK_COLOR: ManipulationState = MANIPULATION_STATE.STOPPED; // Notify end ImageUtility.float2 UV0 = Client2ImageUV(m_ButtonDownMousePosition); ImageUtility.float2 UV1 = Client2ImageUV(e.Location); m_ColorPickingEndDelegate(UV0, UV1); break; } Invalidate(); Capture = false; Cursor = Cursors.Default; }
/// <summary> /// Computes the average color within a rectangle in UV space /// </summary> /// <param name="_TopLeft">The top left corner (in UV space) of the rectangle to sample</param> /// <param name="_BottomRight">The bottom right corner (in UV space) of the rectangle to sample</param> /// <returns>The average xyY color</returns> public ImageUtility.float3 ComputeAverageSwatchColor(ImageUtility.float2 _TopLeft, ImageUtility.float2 _BottomRight) { // Average xyY values in the specified rectangle int X0 = Math.Max(0, Math.Min(m_Texture.Width - 1, (int)Math.Floor(_TopLeft.x * m_Texture.Width))); int Y0 = Math.Max(0, Math.Min(m_Texture.Height - 1, (int)Math.Floor(_TopLeft.y * m_Texture.Height))); int X1 = Math.Min(m_Texture.Width, Math.Max(X0 + 1, (int)Math.Floor(_BottomRight.x * m_Texture.Width))); int Y1 = Math.Min(m_Texture.Height, Math.Max(Y0 + 1, (int)Math.Floor(_BottomRight.y * m_Texture.Height))); int W = X1 - X0; int H = Y1 - Y0; ImageUtility.float4 AverageXYZ = new ImageUtility.float4(0, 0, 0, 0); for (int Y = Y0; Y < Y1; Y++) { for (int X = X0; X < X1; X++) { ImageUtility.float4 XYZ = m_Texture.ContentXYZ[X, Y]; AverageXYZ += XYZ; } } AverageXYZ = (1.0f / (W * H)) * AverageXYZ; ImageUtility.float3 xyY = ImageUtility.ColorProfile.XYZ2xyY((ImageUtility.float3)AverageXYZ); return(xyY); }
protected override void OnMouseMove( MouseEventArgs e ) { base.OnMouseMove( e ); m_MousePositionCurrent = e.Location; if ( m_MouseButtonsDown == MouseButtons.None ) { m_ButtonDownMousePosition = e.Location; m_ButtonDownCropRectangleCenter = m_CropRectangleCenter; m_ButtonDownCropRectangleHalfSize = m_CropRectangleHalfSize; m_ButtonDownCropRectangleRotation = m_CropRectangleRotation; m_ButtonDownCropRectangleAxisX = m_CropRectangleAxisX; m_ButtonDownCropRectangleAxisY = m_CropRectangleAxisY; m_ButtonDownClientCropRectangleCenter = m_ClientCropRectangleCenter; m_ButtonDownClientCropRectangleVertices = new PointF[4] { m_ClientCropRectangleVertices[0], m_ClientCropRectangleVertices[1], m_ClientCropRectangleVertices[2], m_ClientCropRectangleVertices[3], }; } switch ( m_ManipulationState ) { case MANIPULATION_STATE.CROP_RECTANGLE: { // Take care of crop rectangle manipulation if ( !m_CropRectangleManipulationStarted ) { // Update the cursor based on the hovered manipulation spot m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.NONE; const float TOLERANCE = 8.0f; PointF P = e.Location; PointF TopLeftCorner2UV = new PointF( P.X - m_ClientCropRectangleVertices[0].X, P.Y - m_ClientCropRectangleVertices[0].Y ); PointF BottomRightCorner2UV = new PointF( P.X - m_ClientCropRectangleVertices[3].X, P.Y - m_ClientCropRectangleVertices[3].Y ); float Distance2Left = -TopLeftCorner2UV.X * m_CropRectangleAxisX.x - TopLeftCorner2UV.Y * m_CropRectangleAxisX.y; float Distance2Top = TopLeftCorner2UV.X * m_CropRectangleAxisY.x + TopLeftCorner2UV.Y * m_CropRectangleAxisY.y; float Distance2Right = BottomRightCorner2UV.X * m_CropRectangleAxisX.x + BottomRightCorner2UV.Y * m_CropRectangleAxisX.y; float Distance2Bottom = -BottomRightCorner2UV.X * m_CropRectangleAxisY.x - BottomRightCorner2UV.Y * m_CropRectangleAxisY.y; // Stretch if ( Math.Abs( Distance2Left ) < TOLERANCE ) { if ( Math.Abs( Distance2Top ) < TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CORNER_TOP_LEFT; else if ( Math.Abs( Distance2Bottom ) < TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CORNER_BOTTOM_LEFT; else m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.LEFT; } else if ( Math.Abs( Distance2Right ) < TOLERANCE ) { if ( Math.Abs( Distance2Top ) < TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CORNER_TOP_RIGHT; else if ( Math.Abs( Distance2Bottom ) < TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CORNER_BOTTOM_RIGHT; else m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.RIGHT; } else if ( Math.Abs( Distance2Top ) < TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.TOP; else if ( Math.Abs( Distance2Bottom ) < TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.BOTTOM; // Rotate else if ( Distance2Top > TOLERANCE && Distance2Right > TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.ROTATE_TOP_RIGHT; else if ( Distance2Top > TOLERANCE && Distance2Left > TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.ROTATE_TOP_LEFT; else if ( Distance2Bottom > TOLERANCE && Distance2Right > TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_RIGHT; else if ( Distance2Bottom > TOLERANCE && Distance2Left > TOLERANCE ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_LEFT; else if ( Distance2Left < 0.0f && Distance2Right < 0.0f && Distance2Bottom < 0.0f && Distance2Top < 0.0f ) m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CENTER; // Update cursor accordingly switch ( m_CropRectangleManipulatedSpot ) { case CROP_RECTANGLE_SPOT.NONE: Cursor = Cursors.Default; break; case CROP_RECTANGLE_SPOT.LEFT: Cursor = Cursors.SizeWE; break; case CROP_RECTANGLE_SPOT.RIGHT: Cursor = Cursors.SizeWE; break; case CROP_RECTANGLE_SPOT.TOP: Cursor = Cursors.SizeNS; break; case CROP_RECTANGLE_SPOT.BOTTOM: Cursor = Cursors.SizeNS; break; case CROP_RECTANGLE_SPOT.CORNER_TOP_LEFT: Cursor = Cursors.SizeNWSE; break; case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_RIGHT: Cursor = Cursors.SizeNWSE; break; case CROP_RECTANGLE_SPOT.CORNER_TOP_RIGHT: Cursor = Cursors.SizeNESW; break; case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_LEFT: Cursor = Cursors.SizeNESW; break; case CROP_RECTANGLE_SPOT.ROTATE_TOP_LEFT: case CROP_RECTANGLE_SPOT.ROTATE_TOP_RIGHT: case CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_LEFT: case CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_RIGHT: Cursor = Cursors.Hand; break; case CROP_RECTANGLE_SPOT.CENTER: Cursor = Cursors.Hand; break; } } else { // Handle actual manipulation ImageUtility.float2 CurClientPos = new ImageUtility.float2( e.X, e.Y ); ImageUtility.float2 OldClientPos = new ImageUtility.float2( m_ButtonDownMousePosition.X, m_ButtonDownMousePosition.Y ); ImageUtility.float2 OldClientCenter = new ImageUtility.float2( m_ButtonDownClientCropRectangleCenter.X, m_ButtonDownClientCropRectangleCenter.Y ); ImageUtility.float2 OldCenter = m_ButtonDownCropRectangleCenter; ImageUtility.float2 OldHalfSize = m_ButtonDownCropRectangleHalfSize; ImageUtility.float2 OldAxisX = m_ButtonDownCropRectangleAxisX; ImageUtility.float2 OldAxisY = m_ButtonDownCropRectangleAxisY; ImageUtility.float2[] OldVertices = new ImageUtility.float2[4] { OldCenter - OldHalfSize.x * OldAxisX + OldHalfSize.y * OldAxisY, OldCenter + OldHalfSize.x * OldAxisX + OldHalfSize.y * OldAxisY, OldCenter - OldHalfSize.x * OldAxisX - OldHalfSize.y * OldAxisY, OldCenter + OldHalfSize.x * OldAxisX - OldHalfSize.y * OldAxisY, }; ImageUtility.float2 CurCenter = OldCenter; ImageUtility.float2 CurHalfSize = OldHalfSize; ImageUtility.float2 CurAxisX = OldAxisX; ImageUtility.float2 CurAxisY = OldAxisY; RectangleF ImageRect = ImageClientRect(); // The image's rectangle in client space switch ( m_CropRectangleManipulatedSpot ) { case CROP_RECTANGLE_SPOT.LEFT: case CROP_RECTANGLE_SPOT.RIGHT: { ImageUtility.float2 Center2OldPos = OldClientPos - OldClientCenter; float OldDistance2Edge = Center2OldPos.Dot( OldAxisX ); ImageUtility.float2 Center2CurPos = CurClientPos - OldClientCenter; float CurDistance2Edge = Center2CurPos.Dot( OldAxisX ); float Delta = CurDistance2Edge - OldDistance2Edge; // This is the amount (in client space) we need to move the left/right border float DeltaUV = Delta / ImageRect.Height; // This is the same amount in UV space DeltaUV *= 0.5f; // We're dealing with halves all along // Increase width of that amount CurHalfSize.x = OldHalfSize.x + (m_CropRectangleManipulatedSpot == CROP_RECTANGLE_SPOT.LEFT ? -1 : 1) * DeltaUV; // Move center along the X axis if ( m_CropRectangleManipulatedSpot == CROP_RECTANGLE_SPOT.LEFT ) { // Keep right fixed, move to the left ImageUtility.float2 Right = OldCenter + OldHalfSize.x * OldAxisX; ImageUtility.float2 Left = Right - 2.0f * CurHalfSize.x * OldAxisX; CurCenter = 0.5f * (Right + Left); } else { // Keep left fixed, move to the right ImageUtility.float2 Left = OldCenter - OldHalfSize.x * OldAxisX; ImageUtility.float2 Right = Left + 2.0f * CurHalfSize.x * OldAxisX; CurCenter = 0.5f * (Right + Left); } break; } case CROP_RECTANGLE_SPOT.TOP: case CROP_RECTANGLE_SPOT.BOTTOM: { ImageUtility.float2 Center2OldPos = OldClientPos - OldClientCenter; float OldDistance2Edge = Center2OldPos.Dot( OldAxisY ); ImageUtility.float2 Center2CurPos = CurClientPos - OldClientCenter; float CurDistance2Edge = Center2CurPos.Dot( OldAxisY ); float Delta = CurDistance2Edge - OldDistance2Edge; // This is the amount (in client space) we need to move the left/right border float DeltaUV = Delta / ImageRect.Height; // This is the same amount in UV space DeltaUV *= 0.5f; // We're dealing with halves all along // Increase height of that amount CurHalfSize.y = OldHalfSize.y + (m_CropRectangleManipulatedSpot == CROP_RECTANGLE_SPOT.TOP ? 1 : -1) * DeltaUV; // Move center along the X axis if ( m_CropRectangleManipulatedSpot == CROP_RECTANGLE_SPOT.TOP ) { // Keep bottom fixed, move up ImageUtility.float2 Bottom = OldCenter - OldHalfSize.y * OldAxisY; ImageUtility.float2 Top = Bottom + 2.0f * CurHalfSize.y * OldAxisY; CurCenter = 0.5f * (Bottom + Top); } else { // Keep top fixed, move down ImageUtility.float2 Top = OldCenter + OldHalfSize.y * OldAxisY; ImageUtility.float2 Bottom = Top - 2.0f * CurHalfSize.y * OldAxisY; CurCenter = 0.5f * (Bottom + Top); } break; } case CROP_RECTANGLE_SPOT.CORNER_TOP_LEFT: case CROP_RECTANGLE_SPOT.CORNER_TOP_RIGHT: case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_LEFT: case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_RIGHT: { ImageUtility.float2 Delta = CurClientPos - OldClientPos; ImageUtility.float2 DeltaUV = (1.0f / ImageRect.Height) * Delta; ImageUtility.float2 Corner = new ImageUtility.float2(), OppositeCorner = new ImageUtility.float2(); switch ( m_CropRectangleManipulatedSpot ) { case CROP_RECTANGLE_SPOT.CORNER_TOP_LEFT: Corner = OldVertices[0]; OppositeCorner = OldVertices[3]; break; // Keep bottom right fixed case CROP_RECTANGLE_SPOT.CORNER_TOP_RIGHT: Corner = OldVertices[1]; OppositeCorner = OldVertices[2]; break; // Keep bottom left fixed case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_LEFT: Corner = OldVertices[2]; OppositeCorner = OldVertices[1]; break; // Keep top right fixed case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_RIGHT: Corner = OldVertices[3]; OppositeCorner = OldVertices[0]; break; // Keep top left fixed } // Move corner Corner += DeltaUV; // Compute new center CurCenter = new ImageUtility.float2( 0.5f * (Corner.x + OppositeCorner.x), 0.5f * (Corner.y + OppositeCorner.y) ); // Compute new size CurHalfSize = new ImageUtility.float2( Math.Abs( (Corner - CurCenter).Dot( OldAxisX ) ), Math.Abs( (Corner - CurCenter).Dot( OldAxisY ) ) ); break; } case CROP_RECTANGLE_SPOT.ROTATE_TOP_LEFT: case CROP_RECTANGLE_SPOT.ROTATE_TOP_RIGHT: case CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_LEFT: case CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_RIGHT: { ImageUtility.float2 Center2OldPos = OldClientPos - OldClientCenter; ImageUtility.float2 Center2CurPos = CurClientPos - OldClientCenter; float OldAngle = (float) Math.Atan2( -Center2OldPos.y, Center2OldPos.x ); float CurAngle = (float) Math.Atan2( -Center2CurPos.y, Center2CurPos.x ); m_CropRectangleRotation = m_ButtonDownCropRectangleRotation + CurAngle - OldAngle; CurAxisX = new ImageUtility.float2( (float) Math.Cos( m_CropRectangleRotation ), -(float) Math.Sin( m_CropRectangleRotation ) ); CurAxisY = new ImageUtility.float2( -(float) Math.Sin( m_CropRectangleRotation ), -(float) Math.Cos( m_CropRectangleRotation ) ); break; } case CROP_RECTANGLE_SPOT.CENTER: { ImageUtility.float2 Delta = CurClientPos - OldClientPos; ImageUtility.float2 DeltaUV = (1.0f / ImageRect.Height) * Delta; CurCenter = OldCenter + DeltaUV; break; } } CurHalfSize.x = Math.Max( 1e-3f, CurHalfSize.x ); CurHalfSize.y = Math.Max( 1e-3f, CurHalfSize.y ); // Constrain vertices to the image ImageUtility.float2[] Vertices = BuildClipVertices( CurCenter, CurHalfSize, CurAxisX, CurAxisY ); float MinX = 0.5f * (1.0f - ImageAspectRatio); float MaxX = 0.5f * (1.0f + ImageAspectRatio); for ( int i=0; i < 4; i++ ) { bool Rebuild = false; if ( Vertices[i].x < MinX ) { Vertices[i].x = MinX; Rebuild = true; } if ( Vertices[i].x > MaxX ) { Vertices[i].x = MaxX; Rebuild = true; } if ( Vertices[i].y < 0.0f ) { Vertices[i].y = 0.0f; Rebuild = true; } if ( Vertices[i].y > 1.0f ) { Vertices[i].y = 1.0f; Rebuild = true; } if ( !Rebuild ) continue; ImageUtility.float2 OppositeVertex = Vertices[3-i]; // This one is fixed // Recompute center & half size CurCenter = 0.5f * (OppositeVertex + Vertices[i]); ImageUtility.float2 Delta = Vertices[i] - OppositeVertex; CurHalfSize = 0.5f * new ImageUtility.float2( Math.Abs( Delta.Dot( CurAxisX ) ), Math.Abs( Delta.Dot( CurAxisY ) ) ); // Rebuild new vertices Vertices = BuildClipVertices( CurCenter, CurHalfSize, CurAxisX, CurAxisY ); } m_CropRectangleCenter = CurCenter; m_CropRectangleHalfSize = CurHalfSize; // The crop rectangle has changed! m_CropRectangleIsDefault = false; // Repaint to update the crop rectangle Invalidate(); } break; } case MANIPULATION_STATE.CALIBRATION_TARGET: { // Take care of calibration manipulation Cursor = Cursors.Cross; switch ( m_CalibrationStage ) { case CALIBRATION_STAGE.PICK_CENTER: m_CalibrationCenter = Client2ImageUV_NoSquareAspectRatio( e.Location ); Invalidate(); break; case CALIBRATION_STAGE.SET_RADIUS: { ImageUtility.float2 Temp = Client2ImageUV_NoSquareAspectRatio( e.Location ); m_CalibrationRadius = (float) Math.Sqrt( (Temp.x - m_CalibrationCenter.x)*(Temp.x - m_CalibrationCenter.x) + (Temp.y - m_CalibrationCenter.y)*(Temp.y - m_CalibrationCenter.y) ); Invalidate(); } break; } break; } default: Cursor = Cursors.Default; break; } }
private PointF ImageUV2Client_NoSquareAspectRatio(ImageUtility.float2 _Position) { RectangleF ImageRect = ImageClientRect(); return(new PointF(ImageRect.X + _Position.x * ImageRect.Width, ImageRect.Y + _Position.y * ImageRect.Height)); }
private PointF ImageUV2Client(ImageUtility.float2 _Position) { RectangleF ImageRect = ImageClientRect(); return(new PointF(_Position.x * ImageRect.Width + ImageRect.X, _Position.y * ImageRect.Height + ImageRect.Y)); }
/// <summary> /// Captures the calibrated texture /// </summary> /// <param name="_Source">The source image to capture</param> /// <param name="_Database">Database to perform proper calibration</param> /// <param name="_Parms">Parameters for the capture</param> public void Capture( ImageUtility.Bitmap _Source, CameraCalibrationDatabase _Database, CaptureParms _Parms ) { if ( _Source == null ) throw new Exception( "Invalid source bitmap to build texture from!" ); if ( _Database == null ) throw new Exception( "Invalid calibration database found in parameters!" ); if ( _Parms == null ) throw new Exception( "Invalid calibration parameters!" ); if ( m_SwatchWidth <= 0 || m_SwatchHeight <= 0 ) throw new Exception( "Invalid swatch size! Must be > 0!" ); // Save parameters as they're associated to this texture m_CaptureParameters = _Parms; m_WhiteReflectanceReference = _Database.WhiteReflectanceReference; m_WhiteReflectanceCorrectionFactor = _Database.WhiteReflectanceCorrectionFactor; m_SpatialCorrectionEnabled = _Database.WhiteReferenceImage != null; ////////////////////////////////////////////////////////////////////////// // Setup the database to find the most appropriate calibration data for our image infos _Database.PrepareCalibrationFor( _Parms.ISOSpeed, _Parms.ShutterSpeed, _Parms.Aperture ); ////////////////////////////////////////////////////////////////////////// // Build target texture ImageUtility.float4 AvgXYZ = new ImageUtility.float4( 0, 0, 0, 0 ); //DEBUG // float MinLuminance_Raw = float.MaxValue; // float MaxLuminance_Raw = -float.MaxValue; const int EXTREME_VALUES_COUNT = 100; ImageUtility.float3[] ArrayMin = new ImageUtility.float3[EXTREME_VALUES_COUNT]; ImageUtility.float3[] ArrayMax = new ImageUtility.float3[EXTREME_VALUES_COUNT]; for ( int i=0; i < EXTREME_VALUES_COUNT; i++ ) { ArrayMin[i] = new ImageUtility.float3( 0, 1, 0 ); ArrayMax[i] = new ImageUtility.float3( 0, 0, 0 ); } if ( _Parms.CropSource ) { float fImageWidth = 2.0f * _Parms.CropRectangleHalfSize.x * _Source.Height; float fImageHeight = 2.0f * _Parms.CropRectangleHalfSize.y * _Source.Height; int W = (int) Math.Floor( fImageWidth ); int H = (int) Math.Floor( fImageHeight ); ImageUtility.float2 AxisX = new ImageUtility.float2( (float) Math.Cos( _Parms.CropRectangleRotation ), -(float) Math.Sin( _Parms.CropRectangleRotation ) ); ImageUtility.float2 AxisY = new ImageUtility.float2( (float) Math.Sin( _Parms.CropRectangleRotation ), (float) Math.Cos( _Parms.CropRectangleRotation ) ); ImageUtility.float2 TopLeftCorner = new ImageUtility.float2( 0.5f * (_Source.Width - _Source.Height) + _Parms.CropRectangleCenter.x * _Source.Height, _Source.Height * _Parms.CropRectangleCenter.y ) + _Source.Height * (-_Parms.CropRectangleHalfSize.x * AxisX - _Parms.CropRectangleHalfSize.y * AxisY); m_Texture = new ImageUtility.Bitmap( W, H, new ImageUtility.ColorProfile( ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB ) ); ImageUtility.float4 XYZ; ImageUtility.float3 ShortXYZ; ImageUtility.float3 xyY; ImageUtility.float2 CurrentScanlinePixel = TopLeftCorner + 0.5f * (fImageWidth - W) * AxisX + 0.5f * (fImageHeight - H) * AxisY; if ( Math.Abs( _Parms.CropRectangleRotation ) < 1e-6f ) { // Use integer pixels to avoid attenuated values due to bilinear filtering CurrentScanlinePixel.x = (float) Math.Floor( CurrentScanlinePixel.x ); CurrentScanlinePixel.y = (float) Math.Floor( CurrentScanlinePixel.y ); } for ( int Y=0; Y < H; Y++ ) { ImageUtility.float2 CurrentPixel = CurrentScanlinePixel; for ( int X=0; X < W; X++ ) { float U = CurrentPixel.x / _Source.Width; float V = CurrentPixel.y / _Source.Height; XYZ = _Source.BilinearSample( CurrentPixel.x, CurrentPixel.y ); //DEBUG // float L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V ); // if ( L < MinLuminance_Raw ) // MinLuminance_Raw = L; // if ( L > MaxLuminance_Raw ) // MaxLuminance_Raw = L; //DEBUG xyY = ImageUtility.ColorProfile.XYZ2xyY( (ImageUtility.float3) XYZ ); xyY = _Database.CalibrateWithSpatialCorrection( U, V, xyY ); // Apply luminance calibration ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ( xyY ); XYZ = new ImageUtility.float4( ShortXYZ, XYZ.w ); m_Texture.ContentXYZ[X,Y] = XYZ; // Update min/max/avg values InsertMinMax( ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT ); AvgXYZ += XYZ; CurrentPixel += AxisX; } CurrentScanlinePixel += AxisY; } } else { // Simple texture copy, with luminance calibration m_Texture = new ImageUtility.Bitmap( _Source.Width, _Source.Height, new ImageUtility.ColorProfile( ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB ) ); ImageUtility.float4 XYZ; ImageUtility.float3 ShortXYZ; ImageUtility.float3 xyY; int W = m_Texture.Width; int H = m_Texture.Height; int X0 = 0; int X1 = W; int Y0 = 0; int Y1 = H; //DEBUG // X0 = 1088; Y0 = 764; // X1 = X0 + 1100; Y1 = Y0 + 632; for ( int Y=Y0; Y < Y1; Y++ ) { float V = (float) Y / H; for ( int X=X0; X < X1; X++ ) { float U = (float) X / W; XYZ = _Source.ContentXYZ[X,Y]; //DEBUG // float L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V ); // if ( L < MinLuminance_Raw ) // MinLuminance_Raw = L; // if ( L > MaxLuminance_Raw ) // MaxLuminance_Raw = L; //DEBUG xyY = ImageUtility.ColorProfile.XYZ2xyY( (ImageUtility.float3) XYZ ); xyY = _Database.CalibrateWithSpatialCorrection( U, V, xyY ); // Apply luminance calibration ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ( xyY ); XYZ = new ImageUtility.float4( ShortXYZ, XYZ.w ); m_Texture.ContentXYZ[X,Y] = XYZ; // Update min/max/avg values InsertMinMax( ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT ); AvgXYZ += XYZ; } } } // Normalize average swatch color float Normalizer = 1.0f / (m_Texture.Width*m_Texture.Height); ImageUtility.float3 avgxyY = ImageUtility.ColorProfile.XYZ2xyY( Normalizer * ((ImageUtility.float3) AvgXYZ) ); m_SwatchAvg.xyY = avgxyY; // Compute min & max using statistical norm ImageUtility.float3 BestXYZ_Min; ImageUtility.float3 BestXYZ_Max; if ( _Parms.UseModeInsteadOfMean ) { // Use mode BestXYZ_Min = ComputeMode( ArrayMin ); BestXYZ_Max = ComputeMode( ArrayMax ); } else { // Use mean BestXYZ_Min = ComputeMean( ArrayMin ); BestXYZ_Max = ComputeMean( ArrayMax ); } m_SwatchMin.xyY = ImageUtility.ColorProfile.XYZ2xyY( BestXYZ_Min ); m_SwatchMax.xyY = ImageUtility.ColorProfile.XYZ2xyY( BestXYZ_Max ); m_SwatchMin.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchMin.xyY ); m_SwatchMax.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchMax.xyY ); m_SwatchAvg.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchAvg.xyY ); // Rebuild custom swatches foreach ( CustomSwatch CS in m_CustomSwatches ) CS.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, CS.xyY ); ////////////////////////////////////////////////////////////////////////// // Feed some purely informational shot infos to the main texture, probably won't be saved anyway... m_Texture.HasValidShotInfo = true; m_Texture.ISOSpeed = _Parms.ISOSpeed; m_Texture.ShutterSpeed = _Parms.ShutterSpeed; m_Texture.Aperture = _Parms.Aperture; }
/// <summary> /// Prepares the interpolated calibration table to process the pixels in an image shot with the specified shot infos /// </summary> /// <param name="_ISOSpeed"></param> /// <param name="_ShutterSpeed"></param> /// <param name="_Aperture"></param> public void PrepareCalibrationFor( float _ISOSpeed, float _ShutterSpeed, float _Aperture ) { if ( m_RootNode == null ) throw new Exception( "Calibration grid hasn't been built: did you provide a valid database path? Does the path contain camera calibration data?" ); if ( IsPreparedFor( _ISOSpeed, _ShutterSpeed, _Aperture ) ) return; // Already prepared! ////////////////////////////////////////////////////////////////////////// // Find the 8 nodes encompassing our values // I'm making the delicate assumption that, although the starting node is chosen on the // condition its EV values are strictly inferior to the target we're looking for, all // neighbor nodes should satisfy the condition they're properly placed. // // This is true for the direct neighbors +X, +Y, +Z that are immediately above target values // but for example, neighbor (+X +Y) may have a very bad aperture value (Z) that may be // above the target aperture... // // Let's hope the user won't provide too fancy calibrations... // (anyway, interpolants are clamped in [0,1] so there's no risk of overshooting) // ImageUtility.float3 EV; GridNode.Convert2EV( _ISOSpeed, _ShutterSpeed, _Aperture, out EV.x, out EV.y, out EV.z ); // Find the start node GridNode StartNode = FindStartNode( EV.x, EV.y, EV.z ); m_InterpolationStartNode = StartNode; // Build the 8 grid nodes from it GridNode[,,] Grid = new GridNode[2,2,2]; Grid[0,0,0] = StartNode; Grid[1,0,0] = StartNode.m_Neighbors[0][1] != null ? StartNode.m_Neighbors[0][1] : StartNode; // +X Grid[0,1,0] = StartNode.m_Neighbors[1][1] != null ? StartNode.m_Neighbors[1][1] : StartNode; // +Y Grid[0,0,1] = StartNode.m_Neighbors[2][1] != null ? StartNode.m_Neighbors[2][1] : StartNode; // +Z Grid[1,1,0] = Grid[1,0,0].m_Neighbors[1][1] != null ? Grid[1,0,0].m_Neighbors[1][1] : Grid[1,0,0]; // +X +Y Grid[0,1,1] = Grid[0,1,0].m_Neighbors[2][1] != null ? Grid[0,1,0].m_Neighbors[2][1] : Grid[0,1,0]; // +Y +Z Grid[1,0,1] = Grid[0,0,1].m_Neighbors[0][1] != null ? Grid[0,0,1].m_Neighbors[0][1] : Grid[0,0,1]; // +X +Z Grid[1,1,1] = Grid[1,1,0].m_Neighbors[2][1] != null ? Grid[1,1,0].m_Neighbors[2][1] : Grid[1,1,0]; // +X +Y +Z ////////////////////////////////////////////////////////////////////////// // Create the successive interpolants for trilinear interpolation // // Assume we interpolate on X first (ISO speed), so we need 4 distinct values ImageUtility.float4 tX = new ImageUtility.float4( Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,0,0].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,0,0].m_EV_ISOSpeed - Grid[0,0,0].m_EV_ISOSpeed) ) ), // Y=0 Z=0 Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,1,0].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,1,0].m_EV_ISOSpeed - Grid[0,1,0].m_EV_ISOSpeed) ) ), // Y=1 Z=0 Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,0,1].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,0,1].m_EV_ISOSpeed - Grid[0,0,1].m_EV_ISOSpeed) ) ), // Y=0 Z=1 Math.Max( 0.0f, Math.Min( 1.0f, (EV.x - Grid[0,1,1].m_EV_ISOSpeed) / Math.Max( 1e-6f, Grid[1,1,1].m_EV_ISOSpeed - Grid[0,1,1].m_EV_ISOSpeed) ) ) // Y=1 Z=1 ); ImageUtility.float4 rX = new ImageUtility.float4( 1.0f - tX.x, 1.0f - tX.y, 1.0f - tX.z, 1.0f - tX.w ); // Compute the 4 interpolated shutter speeds & apertures ImageUtility.float4 ShutterSpeedsX = new ImageUtility.float4( rX.x * Grid[0,0,0].m_EV_ShutterSpeed + tX.x * Grid[1,0,0].m_EV_ShutterSpeed, // Y=0 Z=0 rX.y * Grid[0,1,0].m_EV_ShutterSpeed + tX.y * Grid[1,1,0].m_EV_ShutterSpeed, // Y=1 Z=0 rX.z * Grid[0,0,1].m_EV_ShutterSpeed + tX.z * Grid[1,0,1].m_EV_ShutterSpeed, // Y=0 Z=1 rX.w * Grid[0,1,1].m_EV_ShutterSpeed + tX.w * Grid[1,1,1].m_EV_ShutterSpeed // Y=1 Z=1 ); ImageUtility.float4 AperturesX = new ImageUtility.float4( rX.x * Grid[0,0,0].m_EV_Aperture + tX.x * Grid[1,0,0].m_EV_Aperture, // Y=0 Z=0 rX.y * Grid[0,1,0].m_EV_Aperture + tX.y * Grid[1,1,0].m_EV_Aperture, // Y=1 Z=0 rX.z * Grid[0,0,1].m_EV_Aperture + tX.z * Grid[1,0,1].m_EV_Aperture, // Y=0 Z=1 rX.w * Grid[0,1,1].m_EV_Aperture + tX.w * Grid[1,1,1].m_EV_Aperture // Y=1 Z=1 ); // Next we interpolate on Y (Shutter speed), so we need 2 distinct values ImageUtility.float2 tY = new ImageUtility.float2( Math.Max( 0.0f, Math.Min( 1.0f, (EV.y - ShutterSpeedsX.x) / Math.Max( 1e-6f, ShutterSpeedsX.y - ShutterSpeedsX.x) ) ), // Z=0 Math.Max( 0.0f, Math.Min( 1.0f, (EV.y - ShutterSpeedsX.z) / Math.Max( 1e-6f, ShutterSpeedsX.w - ShutterSpeedsX.z) ) ) // Z=1 ); ImageUtility.float2 rY = new ImageUtility.float2( 1.0f - tY.x, 1.0f - tY.y ); // Compute the 2 apertures ImageUtility.float2 AperturesY = new ImageUtility.float2( rY.x * AperturesX.x + tY.x * AperturesX.y, rY.y * AperturesX.z + tY.y * AperturesX.w ); // Finally, we interpolate on Z (Aperture), we need only 1 single value float tZ = Math.Max( 0.0f, Math.Min( 1.0f, (EV.z - AperturesY.x) / Math.Max( 1e-6f, AperturesY.y - AperturesY.x) ) ); float rZ = 1.0f - tZ; ////////////////////////////////////////////////////////////////////////// // Create the special camera calibration that is the result of the interpolation of the 8 nearest ones in the grid m_InterpolatedCalibration = new CameraCalibration(); m_InterpolatedCalibration.m_CameraShotInfos.m_ISOSpeed = _ISOSpeed; m_InterpolatedCalibration.m_CameraShotInfos.m_ShutterSpeed = _ShutterSpeed; m_InterpolatedCalibration.m_CameraShotInfos.m_Aperture = _Aperture; for ( int ProbeIndex=0; ProbeIndex < REQUIRED_PROBES_COUNT; ProbeIndex++ ) { CameraCalibration.Probe TargetProbe = m_InterpolatedCalibration.m_Reflectances[ProbeIndex]; float L000 = Grid[0,0,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L100 = Grid[1,0,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L010 = Grid[0,1,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L110 = Grid[1,1,0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L001 = Grid[0,0,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L101 = Grid[1,0,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L011 = Grid[0,1,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L111 = Grid[1,1,1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; // Interpolate on X (ISO speed) float L00 = rX.x * L000 + tX.x * L100; float L10 = rX.y * L010 + tX.y * L110; float L01 = rX.z * L001 + tX.z * L101; float L11 = rX.w * L011 + tX.w * L111; // Interpolate on Y (shutter speed) float L0 = rY.x * L00 + tY.x * L10; float L1 = rY.y * L01 + tY.y * L11; // Interpolate on Z (aperture) float L = rZ * L0 + tZ * L1; TargetProbe.m_IsAvailable = true; TargetProbe.m_LuminanceMeasured = L; } // Fill missing values m_InterpolatedCalibration.UpdateAllLuminances(); // Reset white reflectance reference because it was set for another setup WhiteReflectanceReference = new ImageUtility.float3( 0, 0, -1 ); }
/// <summary> /// Captures the calibrated texture /// </summary> /// <param name="_Source">The source image to capture</param> /// <param name="_Database">Database to perform proper calibration</param> /// <param name="_Parms">Parameters for the capture</param> public void Capture(ImageUtility.Bitmap _Source, CameraCalibrationDatabase _Database, CaptureParms _Parms) { if (_Source == null) { throw new Exception("Invalid source bitmap to build texture from!"); } if (_Database == null) { throw new Exception("Invalid calibration database found in parameters!"); } if (_Parms == null) { throw new Exception("Invalid calibration parameters!"); } if (m_SwatchWidth <= 0 || m_SwatchHeight <= 0) { throw new Exception("Invalid swatch size! Must be > 0!"); } // Save parameters as they're associated to this texture m_CaptureParameters = _Parms; m_WhiteReflectanceReference = _Database.WhiteReflectanceReference; m_WhiteReflectanceCorrectionFactor = _Database.WhiteReflectanceCorrectionFactor; m_SpatialCorrectionEnabled = _Database.WhiteReferenceImage != null; ////////////////////////////////////////////////////////////////////////// // Setup the database to find the most appropriate calibration data for our image infos _Database.PrepareCalibrationFor(_Parms.ISOSpeed, _Parms.ShutterSpeed, _Parms.Aperture); ////////////////////////////////////////////////////////////////////////// // Build target texture ImageUtility.float4 AvgXYZ = new ImageUtility.float4(0, 0, 0, 0); //DEBUG // float MinLuminance_Raw = float.MaxValue; // float MaxLuminance_Raw = -float.MaxValue; const int EXTREME_VALUES_COUNT = 100; ImageUtility.float3[] ArrayMin = new ImageUtility.float3[EXTREME_VALUES_COUNT]; ImageUtility.float3[] ArrayMax = new ImageUtility.float3[EXTREME_VALUES_COUNT]; for (int i = 0; i < EXTREME_VALUES_COUNT; i++) { ArrayMin[i] = new ImageUtility.float3(0, 1, 0); ArrayMax[i] = new ImageUtility.float3(0, 0, 0); } if (_Parms.CropSource) { float fImageWidth = 2.0f * _Parms.CropRectangleHalfSize.x * _Source.Height; float fImageHeight = 2.0f * _Parms.CropRectangleHalfSize.y * _Source.Height; int W = (int)Math.Floor(fImageWidth); int H = (int)Math.Floor(fImageHeight); ImageUtility.float2 AxisX = new ImageUtility.float2((float)Math.Cos(_Parms.CropRectangleRotation), -(float)Math.Sin(_Parms.CropRectangleRotation)); ImageUtility.float2 AxisY = new ImageUtility.float2((float)Math.Sin(_Parms.CropRectangleRotation), (float)Math.Cos(_Parms.CropRectangleRotation)); ImageUtility.float2 TopLeftCorner = new ImageUtility.float2(0.5f * (_Source.Width - _Source.Height) + _Parms.CropRectangleCenter.x * _Source.Height, _Source.Height * _Parms.CropRectangleCenter.y) + _Source.Height * (-_Parms.CropRectangleHalfSize.x * AxisX - _Parms.CropRectangleHalfSize.y * AxisY); m_Texture = new ImageUtility.Bitmap(W, H, new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB)); ImageUtility.float4 XYZ; ImageUtility.float3 ShortXYZ; ImageUtility.float3 xyY; ImageUtility.float2 CurrentScanlinePixel = TopLeftCorner + 0.5f * (fImageWidth - W) * AxisX + 0.5f * (fImageHeight - H) * AxisY; if (Math.Abs(_Parms.CropRectangleRotation) < 1e-6f) { // Use integer pixels to avoid attenuated values due to bilinear filtering CurrentScanlinePixel.x = (float)Math.Floor(CurrentScanlinePixel.x); CurrentScanlinePixel.y = (float)Math.Floor(CurrentScanlinePixel.y); } for (int Y = 0; Y < H; Y++) { ImageUtility.float2 CurrentPixel = CurrentScanlinePixel; for (int X = 0; X < W; X++) { float U = CurrentPixel.x / _Source.Width; float V = CurrentPixel.y / _Source.Height; XYZ = _Source.BilinearSample(CurrentPixel.x, CurrentPixel.y); //DEBUG // float L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V ); // if ( L < MinLuminance_Raw ) // MinLuminance_Raw = L; // if ( L > MaxLuminance_Raw ) // MaxLuminance_Raw = L; //DEBUG xyY = ImageUtility.ColorProfile.XYZ2xyY((ImageUtility.float3)XYZ); xyY = _Database.CalibrateWithSpatialCorrection(U, V, xyY); // Apply luminance calibration ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ(xyY); XYZ = new ImageUtility.float4(ShortXYZ, XYZ.w); m_Texture.ContentXYZ[X, Y] = XYZ; // Update min/max/avg values InsertMinMax(ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT); AvgXYZ += XYZ; CurrentPixel += AxisX; } CurrentScanlinePixel += AxisY; } } else { // Simple texture copy, with luminance calibration m_Texture = new ImageUtility.Bitmap(_Source.Width, _Source.Height, new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB)); ImageUtility.float4 XYZ; ImageUtility.float3 ShortXYZ; ImageUtility.float3 xyY; int W = m_Texture.Width; int H = m_Texture.Height; int X0 = 0; int X1 = W; int Y0 = 0; int Y1 = H; //DEBUG // X0 = 1088; Y0 = 764; // X1 = X0 + 1100; Y1 = Y0 + 632; for (int Y = Y0; Y < Y1; Y++) { float V = (float)Y / H; for (int X = X0; X < X1; X++) { float U = (float)X / W; XYZ = _Source.ContentXYZ[X, Y]; //DEBUG // float L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V ); // if ( L < MinLuminance_Raw ) // MinLuminance_Raw = L; // if ( L > MaxLuminance_Raw ) // MaxLuminance_Raw = L; //DEBUG xyY = ImageUtility.ColorProfile.XYZ2xyY((ImageUtility.float3)XYZ); xyY = _Database.CalibrateWithSpatialCorrection(U, V, xyY); // Apply luminance calibration ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ(xyY); XYZ = new ImageUtility.float4(ShortXYZ, XYZ.w); m_Texture.ContentXYZ[X, Y] = XYZ; // Update min/max/avg values InsertMinMax(ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT); AvgXYZ += XYZ; } } } // Normalize average swatch color float Normalizer = 1.0f / (m_Texture.Width * m_Texture.Height); ImageUtility.float3 avgxyY = ImageUtility.ColorProfile.XYZ2xyY(Normalizer * ((ImageUtility.float3)AvgXYZ)); m_SwatchAvg.xyY = avgxyY; // Compute min & max using statistical norm ImageUtility.float3 BestXYZ_Min; ImageUtility.float3 BestXYZ_Max; if (_Parms.UseModeInsteadOfMean) { // Use mode BestXYZ_Min = ComputeMode(ArrayMin); BestXYZ_Max = ComputeMode(ArrayMax); } else { // Use mean BestXYZ_Min = ComputeMean(ArrayMin); BestXYZ_Max = ComputeMean(ArrayMax); } m_SwatchMin.xyY = ImageUtility.ColorProfile.XYZ2xyY(BestXYZ_Min); m_SwatchMax.xyY = ImageUtility.ColorProfile.XYZ2xyY(BestXYZ_Max); m_SwatchMin.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchMin.xyY); m_SwatchMax.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchMax.xyY); m_SwatchAvg.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchAvg.xyY); // Rebuild custom swatches foreach (CustomSwatch CS in m_CustomSwatches) { CS.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, CS.xyY); } ////////////////////////////////////////////////////////////////////////// // Feed some purely informational shot infos to the main texture, probably won't be saved anyway... m_Texture.HasValidShotInfo = true; m_Texture.ISOSpeed = _Parms.ISOSpeed; m_Texture.ShutterSpeed = _Parms.ShutterSpeed; m_Texture.Aperture = _Parms.Aperture; }
/// <summary> /// Prepares the interpolated calibration table to process the pixels in an image shot with the specified shot infos /// </summary> /// <param name="_ISOSpeed"></param> /// <param name="_ShutterSpeed"></param> /// <param name="_Aperture"></param> public void PrepareCalibrationFor(float _ISOSpeed, float _ShutterSpeed, float _Aperture) { if (m_RootNode == null) { throw new Exception("Calibration grid hasn't been built: did you provide a valid database path? Does the path contain camera calibration data?"); } if (IsPreparedFor(_ISOSpeed, _ShutterSpeed, _Aperture)) { return; // Already prepared! } ////////////////////////////////////////////////////////////////////////// // Find the 8 nodes encompassing our values // I'm making the delicate assumption that, although the starting node is chosen on the // condition its EV values are strictly inferior to the target we're looking for, all // neighbor nodes should satisfy the condition they're properly placed. // // This is true for the direct neighbors +X, +Y, +Z that are immediately above target values // but for example, neighbor (+X +Y) may have a very bad aperture value (Z) that may be // above the target aperture... // // Let's hope the user won't provide too fancy calibrations... // (anyway, interpolants are clamped in [0,1] so there's no risk of overshooting) // ImageUtility.float3 EV; GridNode.Convert2EV(_ISOSpeed, _ShutterSpeed, _Aperture, out EV.x, out EV.y, out EV.z); // Find the start node GridNode StartNode = FindStartNode(EV.x, EV.y, EV.z); m_InterpolationStartNode = StartNode; // Build the 8 grid nodes from it GridNode[,,] Grid = new GridNode[2, 2, 2]; Grid[0, 0, 0] = StartNode; Grid[1, 0, 0] = StartNode.m_Neighbors[0][1] != null ? StartNode.m_Neighbors[0][1] : StartNode; // +X Grid[0, 1, 0] = StartNode.m_Neighbors[1][1] != null ? StartNode.m_Neighbors[1][1] : StartNode; // +Y Grid[0, 0, 1] = StartNode.m_Neighbors[2][1] != null ? StartNode.m_Neighbors[2][1] : StartNode; // +Z Grid[1, 1, 0] = Grid[1, 0, 0].m_Neighbors[1][1] != null ? Grid[1, 0, 0].m_Neighbors[1][1] : Grid[1, 0, 0]; // +X +Y Grid[0, 1, 1] = Grid[0, 1, 0].m_Neighbors[2][1] != null ? Grid[0, 1, 0].m_Neighbors[2][1] : Grid[0, 1, 0]; // +Y +Z Grid[1, 0, 1] = Grid[0, 0, 1].m_Neighbors[0][1] != null ? Grid[0, 0, 1].m_Neighbors[0][1] : Grid[0, 0, 1]; // +X +Z Grid[1, 1, 1] = Grid[1, 1, 0].m_Neighbors[2][1] != null ? Grid[1, 1, 0].m_Neighbors[2][1] : Grid[1, 1, 0]; // +X +Y +Z ////////////////////////////////////////////////////////////////////////// // Create the successive interpolants for trilinear interpolation // // Assume we interpolate on X first (ISO speed), so we need 4 distinct values ImageUtility.float4 tX = new ImageUtility.float4( Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 0, 0].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 0, 0].m_EV_ISOSpeed - Grid[0, 0, 0].m_EV_ISOSpeed))), // Y=0 Z=0 Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 1, 0].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 1, 0].m_EV_ISOSpeed - Grid[0, 1, 0].m_EV_ISOSpeed))), // Y=1 Z=0 Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 0, 1].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 0, 1].m_EV_ISOSpeed - Grid[0, 0, 1].m_EV_ISOSpeed))), // Y=0 Z=1 Math.Max(0.0f, Math.Min(1.0f, (EV.x - Grid[0, 1, 1].m_EV_ISOSpeed) / Math.Max(1e-6f, Grid[1, 1, 1].m_EV_ISOSpeed - Grid[0, 1, 1].m_EV_ISOSpeed))) // Y=1 Z=1 ); ImageUtility.float4 rX = new ImageUtility.float4(1.0f - tX.x, 1.0f - tX.y, 1.0f - tX.z, 1.0f - tX.w); // Compute the 4 interpolated shutter speeds & apertures ImageUtility.float4 ShutterSpeedsX = new ImageUtility.float4( rX.x * Grid[0, 0, 0].m_EV_ShutterSpeed + tX.x * Grid[1, 0, 0].m_EV_ShutterSpeed, // Y=0 Z=0 rX.y * Grid[0, 1, 0].m_EV_ShutterSpeed + tX.y * Grid[1, 1, 0].m_EV_ShutterSpeed, // Y=1 Z=0 rX.z * Grid[0, 0, 1].m_EV_ShutterSpeed + tX.z * Grid[1, 0, 1].m_EV_ShutterSpeed, // Y=0 Z=1 rX.w * Grid[0, 1, 1].m_EV_ShutterSpeed + tX.w * Grid[1, 1, 1].m_EV_ShutterSpeed // Y=1 Z=1 ); ImageUtility.float4 AperturesX = new ImageUtility.float4( rX.x * Grid[0, 0, 0].m_EV_Aperture + tX.x * Grid[1, 0, 0].m_EV_Aperture, // Y=0 Z=0 rX.y * Grid[0, 1, 0].m_EV_Aperture + tX.y * Grid[1, 1, 0].m_EV_Aperture, // Y=1 Z=0 rX.z * Grid[0, 0, 1].m_EV_Aperture + tX.z * Grid[1, 0, 1].m_EV_Aperture, // Y=0 Z=1 rX.w * Grid[0, 1, 1].m_EV_Aperture + tX.w * Grid[1, 1, 1].m_EV_Aperture // Y=1 Z=1 ); // Next we interpolate on Y (Shutter speed), so we need 2 distinct values ImageUtility.float2 tY = new ImageUtility.float2( Math.Max(0.0f, Math.Min(1.0f, (EV.y - ShutterSpeedsX.x) / Math.Max(1e-6f, ShutterSpeedsX.y - ShutterSpeedsX.x))), // Z=0 Math.Max(0.0f, Math.Min(1.0f, (EV.y - ShutterSpeedsX.z) / Math.Max(1e-6f, ShutterSpeedsX.w - ShutterSpeedsX.z))) // Z=1 ); ImageUtility.float2 rY = new ImageUtility.float2(1.0f - tY.x, 1.0f - tY.y); // Compute the 2 apertures ImageUtility.float2 AperturesY = new ImageUtility.float2( rY.x * AperturesX.x + tY.x * AperturesX.y, rY.y * AperturesX.z + tY.y * AperturesX.w ); // Finally, we interpolate on Z (Aperture), we need only 1 single value float tZ = Math.Max(0.0f, Math.Min(1.0f, (EV.z - AperturesY.x) / Math.Max(1e-6f, AperturesY.y - AperturesY.x))); float rZ = 1.0f - tZ; ////////////////////////////////////////////////////////////////////////// // Create the special camera calibration that is the result of the interpolation of the 8 nearest ones in the grid m_InterpolatedCalibration = new CameraCalibration(); m_InterpolatedCalibration.m_CameraShotInfos.m_ISOSpeed = _ISOSpeed; m_InterpolatedCalibration.m_CameraShotInfos.m_ShutterSpeed = _ShutterSpeed; m_InterpolatedCalibration.m_CameraShotInfos.m_Aperture = _Aperture; for (int ProbeIndex = 0; ProbeIndex < REQUIRED_PROBES_COUNT; ProbeIndex++) { CameraCalibration.Probe TargetProbe = m_InterpolatedCalibration.m_Reflectances[ProbeIndex]; float L000 = Grid[0, 0, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L100 = Grid[1, 0, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L010 = Grid[0, 1, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L110 = Grid[1, 1, 0].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L001 = Grid[0, 0, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L101 = Grid[1, 0, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L011 = Grid[0, 1, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; float L111 = Grid[1, 1, 1].m_CameraCalibration.m_Reflectances[ProbeIndex].m_LuminanceMeasured; // Interpolate on X (ISO speed) float L00 = rX.x * L000 + tX.x * L100; float L10 = rX.y * L010 + tX.y * L110; float L01 = rX.z * L001 + tX.z * L101; float L11 = rX.w * L011 + tX.w * L111; // Interpolate on Y (shutter speed) float L0 = rY.x * L00 + tY.x * L10; float L1 = rY.y * L01 + tY.y * L11; // Interpolate on Z (aperture) float L = rZ * L0 + tZ * L1; TargetProbe.m_IsAvailable = true; TargetProbe.m_LuminanceMeasured = L; } // Fill missing values m_InterpolatedCalibration.UpdateAllLuminances(); // Reset white reflectance reference because it was set for another setup WhiteReflectanceReference = new ImageUtility.float3(0, 0, -1); }
private void UpdateCropRectangleVertices() { m_CropRectangleAxisX = new ImageUtility.float2( (float) Math.Cos( m_CropRectangleRotation ), -(float) Math.Sin( m_CropRectangleRotation ) ); m_CropRectangleAxisY = new ImageUtility.float2( -(float) Math.Sin( m_CropRectangleRotation ), -(float) Math.Cos( m_CropRectangleRotation ) ); RectangleF ImageRect = ImageClientRect(); m_ClientCropRectangleCenter = ImageUV2Client( m_CropRectangleCenter ); ImageUtility.float2[] Vertices = new ImageUtility.float2[4] { m_CropRectangleCenter - m_CropRectangleHalfSize.x * m_CropRectangleAxisX + m_CropRectangleHalfSize.y * m_CropRectangleAxisY, m_CropRectangleCenter + m_CropRectangleHalfSize.x * m_CropRectangleAxisX + m_CropRectangleHalfSize.y * m_CropRectangleAxisY, m_CropRectangleCenter - m_CropRectangleHalfSize.x * m_CropRectangleAxisX - m_CropRectangleHalfSize.y * m_CropRectangleAxisY, m_CropRectangleCenter + m_CropRectangleHalfSize.x * m_CropRectangleAxisX - m_CropRectangleHalfSize.y * m_CropRectangleAxisY, }; for ( int i=0; i < 4; i++ ) m_ClientCropRectangleVertices[i] = ImageUV2Client( Vertices[i] ); }
protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); m_MousePositionCurrent = e.Location; if (m_MouseButtonsDown == MouseButtons.None) { m_ButtonDownMousePosition = e.Location; m_ButtonDownCropRectangleCenter = m_CropRectangleCenter; m_ButtonDownCropRectangleHalfSize = m_CropRectangleHalfSize; m_ButtonDownCropRectangleRotation = m_CropRectangleRotation; m_ButtonDownCropRectangleAxisX = m_CropRectangleAxisX; m_ButtonDownCropRectangleAxisY = m_CropRectangleAxisY; m_ButtonDownClientCropRectangleCenter = m_ClientCropRectangleCenter; m_ButtonDownClientCropRectangleVertices = new PointF[4] { m_ClientCropRectangleVertices[0], m_ClientCropRectangleVertices[1], m_ClientCropRectangleVertices[2], m_ClientCropRectangleVertices[3], }; } switch (m_ManipulationState) { case MANIPULATION_STATE.CROP_RECTANGLE: { // Take care of crop rectangle manipulation if (!m_CropRectangleManipulationStarted) { // Update the cursor based on the hovered manipulation spot m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.NONE; const float TOLERANCE = 8.0f; PointF P = e.Location; PointF TopLeftCorner2UV = new PointF(P.X - m_ClientCropRectangleVertices[0].X, P.Y - m_ClientCropRectangleVertices[0].Y); PointF BottomRightCorner2UV = new PointF(P.X - m_ClientCropRectangleVertices[3].X, P.Y - m_ClientCropRectangleVertices[3].Y); float Distance2Left = -TopLeftCorner2UV.X * m_CropRectangleAxisX.x - TopLeftCorner2UV.Y * m_CropRectangleAxisX.y; float Distance2Top = TopLeftCorner2UV.X * m_CropRectangleAxisY.x + TopLeftCorner2UV.Y * m_CropRectangleAxisY.y; float Distance2Right = BottomRightCorner2UV.X * m_CropRectangleAxisX.x + BottomRightCorner2UV.Y * m_CropRectangleAxisX.y; float Distance2Bottom = -BottomRightCorner2UV.X * m_CropRectangleAxisY.x - BottomRightCorner2UV.Y * m_CropRectangleAxisY.y; // Stretch if (Math.Abs(Distance2Left) < TOLERANCE) { if (Math.Abs(Distance2Top) < TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CORNER_TOP_LEFT; } else if (Math.Abs(Distance2Bottom) < TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CORNER_BOTTOM_LEFT; } else { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.LEFT; } } else if (Math.Abs(Distance2Right) < TOLERANCE) { if (Math.Abs(Distance2Top) < TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CORNER_TOP_RIGHT; } else if (Math.Abs(Distance2Bottom) < TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CORNER_BOTTOM_RIGHT; } else { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.RIGHT; } } else if (Math.Abs(Distance2Top) < TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.TOP; } else if (Math.Abs(Distance2Bottom) < TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.BOTTOM; } // Rotate else if (Distance2Top > TOLERANCE && Distance2Right > TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.ROTATE_TOP_RIGHT; } else if (Distance2Top > TOLERANCE && Distance2Left > TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.ROTATE_TOP_LEFT; } else if (Distance2Bottom > TOLERANCE && Distance2Right > TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_RIGHT; } else if (Distance2Bottom > TOLERANCE && Distance2Left > TOLERANCE) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_LEFT; } else if (Distance2Left < 0.0f && Distance2Right < 0.0f && Distance2Bottom < 0.0f && Distance2Top < 0.0f) { m_CropRectangleManipulatedSpot = CROP_RECTANGLE_SPOT.CENTER; } // Update cursor accordingly switch (m_CropRectangleManipulatedSpot) { case CROP_RECTANGLE_SPOT.NONE: Cursor = Cursors.Default; break; case CROP_RECTANGLE_SPOT.LEFT: Cursor = Cursors.SizeWE; break; case CROP_RECTANGLE_SPOT.RIGHT: Cursor = Cursors.SizeWE; break; case CROP_RECTANGLE_SPOT.TOP: Cursor = Cursors.SizeNS; break; case CROP_RECTANGLE_SPOT.BOTTOM: Cursor = Cursors.SizeNS; break; case CROP_RECTANGLE_SPOT.CORNER_TOP_LEFT: Cursor = Cursors.SizeNWSE; break; case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_RIGHT: Cursor = Cursors.SizeNWSE; break; case CROP_RECTANGLE_SPOT.CORNER_TOP_RIGHT: Cursor = Cursors.SizeNESW; break; case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_LEFT: Cursor = Cursors.SizeNESW; break; case CROP_RECTANGLE_SPOT.ROTATE_TOP_LEFT: case CROP_RECTANGLE_SPOT.ROTATE_TOP_RIGHT: case CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_LEFT: case CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_RIGHT: Cursor = Cursors.Hand; break; case CROP_RECTANGLE_SPOT.CENTER: Cursor = Cursors.Hand; break; } } else { // Handle actual manipulation ImageUtility.float2 CurClientPos = new ImageUtility.float2(e.X, e.Y); ImageUtility.float2 OldClientPos = new ImageUtility.float2(m_ButtonDownMousePosition.X, m_ButtonDownMousePosition.Y); ImageUtility.float2 OldClientCenter = new ImageUtility.float2(m_ButtonDownClientCropRectangleCenter.X, m_ButtonDownClientCropRectangleCenter.Y); ImageUtility.float2 OldCenter = m_ButtonDownCropRectangleCenter; ImageUtility.float2 OldHalfSize = m_ButtonDownCropRectangleHalfSize; ImageUtility.float2 OldAxisX = m_ButtonDownCropRectangleAxisX; ImageUtility.float2 OldAxisY = m_ButtonDownCropRectangleAxisY; ImageUtility.float2[] OldVertices = new ImageUtility.float2[4] { OldCenter - OldHalfSize.x * OldAxisX + OldHalfSize.y * OldAxisY, OldCenter + OldHalfSize.x * OldAxisX + OldHalfSize.y * OldAxisY, OldCenter - OldHalfSize.x * OldAxisX - OldHalfSize.y * OldAxisY, OldCenter + OldHalfSize.x * OldAxisX - OldHalfSize.y * OldAxisY, }; ImageUtility.float2 CurCenter = OldCenter; ImageUtility.float2 CurHalfSize = OldHalfSize; ImageUtility.float2 CurAxisX = OldAxisX; ImageUtility.float2 CurAxisY = OldAxisY; RectangleF ImageRect = ImageClientRect(); // The image's rectangle in client space switch (m_CropRectangleManipulatedSpot) { case CROP_RECTANGLE_SPOT.LEFT: case CROP_RECTANGLE_SPOT.RIGHT: { ImageUtility.float2 Center2OldPos = OldClientPos - OldClientCenter; float OldDistance2Edge = Center2OldPos.Dot(OldAxisX); ImageUtility.float2 Center2CurPos = CurClientPos - OldClientCenter; float CurDistance2Edge = Center2CurPos.Dot(OldAxisX); float Delta = CurDistance2Edge - OldDistance2Edge; // This is the amount (in client space) we need to move the left/right border float DeltaUV = Delta / ImageRect.Height; // This is the same amount in UV space DeltaUV *= 0.5f; // We're dealing with halves all along // Increase width of that amount CurHalfSize.x = OldHalfSize.x + (m_CropRectangleManipulatedSpot == CROP_RECTANGLE_SPOT.LEFT ? -1 : 1) * DeltaUV; // Move center along the X axis if (m_CropRectangleManipulatedSpot == CROP_RECTANGLE_SPOT.LEFT) { // Keep right fixed, move to the left ImageUtility.float2 Right = OldCenter + OldHalfSize.x * OldAxisX; ImageUtility.float2 Left = Right - 2.0f * CurHalfSize.x * OldAxisX; CurCenter = 0.5f * (Right + Left); } else { // Keep left fixed, move to the right ImageUtility.float2 Left = OldCenter - OldHalfSize.x * OldAxisX; ImageUtility.float2 Right = Left + 2.0f * CurHalfSize.x * OldAxisX; CurCenter = 0.5f * (Right + Left); } break; } case CROP_RECTANGLE_SPOT.TOP: case CROP_RECTANGLE_SPOT.BOTTOM: { ImageUtility.float2 Center2OldPos = OldClientPos - OldClientCenter; float OldDistance2Edge = Center2OldPos.Dot(OldAxisY); ImageUtility.float2 Center2CurPos = CurClientPos - OldClientCenter; float CurDistance2Edge = Center2CurPos.Dot(OldAxisY); float Delta = CurDistance2Edge - OldDistance2Edge; // This is the amount (in client space) we need to move the left/right border float DeltaUV = Delta / ImageRect.Height; // This is the same amount in UV space DeltaUV *= 0.5f; // We're dealing with halves all along // Increase height of that amount CurHalfSize.y = OldHalfSize.y + (m_CropRectangleManipulatedSpot == CROP_RECTANGLE_SPOT.TOP ? 1 : -1) * DeltaUV; // Move center along the X axis if (m_CropRectangleManipulatedSpot == CROP_RECTANGLE_SPOT.TOP) { // Keep bottom fixed, move up ImageUtility.float2 Bottom = OldCenter - OldHalfSize.y * OldAxisY; ImageUtility.float2 Top = Bottom + 2.0f * CurHalfSize.y * OldAxisY; CurCenter = 0.5f * (Bottom + Top); } else { // Keep top fixed, move down ImageUtility.float2 Top = OldCenter + OldHalfSize.y * OldAxisY; ImageUtility.float2 Bottom = Top - 2.0f * CurHalfSize.y * OldAxisY; CurCenter = 0.5f * (Bottom + Top); } break; } case CROP_RECTANGLE_SPOT.CORNER_TOP_LEFT: case CROP_RECTANGLE_SPOT.CORNER_TOP_RIGHT: case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_LEFT: case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_RIGHT: { ImageUtility.float2 Delta = CurClientPos - OldClientPos; ImageUtility.float2 DeltaUV = (1.0f / ImageRect.Height) * Delta; ImageUtility.float2 Corner = new ImageUtility.float2(), OppositeCorner = new ImageUtility.float2(); switch (m_CropRectangleManipulatedSpot) { case CROP_RECTANGLE_SPOT.CORNER_TOP_LEFT: Corner = OldVertices[0]; OppositeCorner = OldVertices[3]; break; // Keep bottom right fixed case CROP_RECTANGLE_SPOT.CORNER_TOP_RIGHT: Corner = OldVertices[1]; OppositeCorner = OldVertices[2]; break; // Keep bottom left fixed case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_LEFT: Corner = OldVertices[2]; OppositeCorner = OldVertices[1]; break; // Keep top right fixed case CROP_RECTANGLE_SPOT.CORNER_BOTTOM_RIGHT: Corner = OldVertices[3]; OppositeCorner = OldVertices[0]; break; // Keep top left fixed } // Move corner Corner += DeltaUV; // Compute new center CurCenter = new ImageUtility.float2(0.5f * (Corner.x + OppositeCorner.x), 0.5f * (Corner.y + OppositeCorner.y)); // Compute new size CurHalfSize = new ImageUtility.float2(Math.Abs((Corner - CurCenter).Dot(OldAxisX)), Math.Abs((Corner - CurCenter).Dot(OldAxisY))); break; } case CROP_RECTANGLE_SPOT.ROTATE_TOP_LEFT: case CROP_RECTANGLE_SPOT.ROTATE_TOP_RIGHT: case CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_LEFT: case CROP_RECTANGLE_SPOT.ROTATE_BOTTOM_RIGHT: { ImageUtility.float2 Center2OldPos = OldClientPos - OldClientCenter; ImageUtility.float2 Center2CurPos = CurClientPos - OldClientCenter; float OldAngle = (float)Math.Atan2(-Center2OldPos.y, Center2OldPos.x); float CurAngle = (float)Math.Atan2(-Center2CurPos.y, Center2CurPos.x); m_CropRectangleRotation = m_ButtonDownCropRectangleRotation + CurAngle - OldAngle; CurAxisX = new ImageUtility.float2((float)Math.Cos(m_CropRectangleRotation), -(float)Math.Sin(m_CropRectangleRotation)); CurAxisY = new ImageUtility.float2(-(float)Math.Sin(m_CropRectangleRotation), -(float)Math.Cos(m_CropRectangleRotation)); break; } case CROP_RECTANGLE_SPOT.CENTER: { ImageUtility.float2 Delta = CurClientPos - OldClientPos; ImageUtility.float2 DeltaUV = (1.0f / ImageRect.Height) * Delta; CurCenter = OldCenter + DeltaUV; break; } } CurHalfSize.x = Math.Max(1e-3f, CurHalfSize.x); CurHalfSize.y = Math.Max(1e-3f, CurHalfSize.y); // Constrain vertices to the image ImageUtility.float2[] Vertices = BuildClipVertices(CurCenter, CurHalfSize, CurAxisX, CurAxisY); float MinX = 0.5f * (1.0f - ImageAspectRatio); float MaxX = 0.5f * (1.0f + ImageAspectRatio); for (int i = 0; i < 4; i++) { bool Rebuild = false; if (Vertices[i].x < MinX) { Vertices[i].x = MinX; Rebuild = true; } if (Vertices[i].x > MaxX) { Vertices[i].x = MaxX; Rebuild = true; } if (Vertices[i].y < 0.0f) { Vertices[i].y = 0.0f; Rebuild = true; } if (Vertices[i].y > 1.0f) { Vertices[i].y = 1.0f; Rebuild = true; } if (!Rebuild) { continue; } ImageUtility.float2 OppositeVertex = Vertices[3 - i]; // This one is fixed // Recompute center & half size CurCenter = 0.5f * (OppositeVertex + Vertices[i]); ImageUtility.float2 Delta = Vertices[i] - OppositeVertex; CurHalfSize = 0.5f * new ImageUtility.float2(Math.Abs(Delta.Dot(CurAxisX)), Math.Abs(Delta.Dot(CurAxisY))); // Rebuild new vertices Vertices = BuildClipVertices(CurCenter, CurHalfSize, CurAxisX, CurAxisY); } m_CropRectangleCenter = CurCenter; m_CropRectangleHalfSize = CurHalfSize; // The crop rectangle has changed! m_CropRectangleIsDefault = false; // Repaint to update the crop rectangle Invalidate(); } break; } case MANIPULATION_STATE.CALIBRATION_TARGET: { // Take care of calibration manipulation Cursor = Cursors.Cross; switch (m_CalibrationStage) { case CALIBRATION_STAGE.PICK_CENTER: m_CalibrationCenter = Client2ImageUV_NoSquareAspectRatio(e.Location); Invalidate(); break; case CALIBRATION_STAGE.SET_RADIUS: { ImageUtility.float2 Temp = Client2ImageUV_NoSquareAspectRatio(e.Location); m_CalibrationRadius = (float)Math.Sqrt((Temp.x - m_CalibrationCenter.x) * (Temp.x - m_CalibrationCenter.x) + (Temp.y - m_CalibrationCenter.y) * (Temp.y - m_CalibrationCenter.y)); Invalidate(); } break; } break; } default: Cursor = Cursors.Default; break; } }
/// <summary> /// Sets the crop rectangle to a specific value /// </summary> public void SetCropRectangle( ImageUtility.float2 _Center, ImageUtility.float2 _HalfSize, float _Rotation ) { m_CropRectangleIsDefault = false; m_CropRectangleCenter = _Center; m_CropRectangleHalfSize = _HalfSize; m_CropRectangleRotation = _Rotation; Invalidate(); }
/// <summary> /// Resets the crop rectangle to the entire image /// </summary> public void ResetCropRectangle() { m_CropRectangleIsDefault = true; if ( m_Image == null ) return; m_CropRectangleCenter = new ImageUtility.float2( 0.5f, 0.5f ); m_CropRectangleHalfSize = new ImageUtility.float2( 0.5f * ImageAspectRatio, 0.5f ); m_CropRectangleRotation = 0.0f; Invalidate(); }