程序员眼中的数学之美:高数 - 映射 (单射、满射、双射、逆映射)


什么是 映射
映射,是现代数学中的一个基本概念 ,包含单射、满射、双射、逆映射、复合映射。
单射:不同的输入映射到不同的输出;
满射:每个可能的输出都有至少一个输入映射到它;
双射:既是单射又是满射;
逆映射:只有一一对应的映射才有逆映射 ,即输入输出对调;
复合映射:把多个映射"串起来",先做A映射,再做B映射。
以下来以动画演示部分概念。
编程工具:VS2022、VSCode、C#、WinUI3(NET8.0)、Compostion API
AI辅助:Claude Code(https://www.cnblogs.com/xingrenh/p/18942223)
演示:非单射-非满射
如:马赛克聚合效果
处理规则:
1,将多个像素映射到同一个大块(违反单射)
2,只覆盖图像的中心区域,留下边缘空白(违反满射)
视觉效果:中心区域呈现马赛克效果,边缘渐变消失
映射特点:
非单射的特点,是会对原数据损失或扭曲,不管是多到一的损失,还是一到多的想象扩展或变形,都对原数据进行了不可逆的破坏。下面演示由清晰的完整图,演变到细节丢失的、模糊的局部图,即 非单非满的马赛克聚合效果。

部分代码:
private void InitializeComposition()
{
var visual = ElementCompositionPreview.GetElementVisual(TargetCanvas);
_compositor = visual.Compositor;
_containerVisual = _compositor.CreateContainerVisual();
ElementCompositionPreview.SetElementChildVisual(TargetCanvas, _containerVisual);
// 初始化动画计时器
_animationTimer = new DispatcherTimer();
_animationTimer.Interval = TimeSpan.FromMilliseconds(16); // 约60fps
_animationTimer.Tick += AnimationTimer_Tick;
}
private void AnimationTimer_Tick(object sender, object e)
{
if (!_isAnimating || _blockVisuals.Count == 0) return;
// 每帧处理的块数量(基于速度)
var blocksPerFrame = Math.Max(1, (int)(5 * _animationSpeed));
// 更新所有已开始的块
for (int i = 0; i <= _currentBlockIndex && i < _blockVisuals.Count; i++)
{
var block = _blockVisuals[i];
if (block.AnimationProgress < 1.0)
{
// 更新动画进度
block.AnimationProgress = Math.Min(1.0, block.AnimationProgress + 0.03 * _animationSpeed);
// 计算插值位置
var progress = EaseInOutCubic(block.AnimationProgress);
if (block.IsActive)
{
// 活动块:移动到目标位置
var currentX = block.SourcePosition.X + (block.TargetPosition.X - block.SourcePosition.X) * progress;
var currentY = block.SourcePosition.Y + (block.TargetPosition.Y - block.SourcePosition.Y) * progress;
Canvas.SetLeft(block.Rectangle, currentX);
Canvas.SetTop(block.Rectangle, currentY);
block.Rectangle.Opacity = Math.Min(1.0, progress * 1.5); // 更快显示
// 颜色增强效果
var brightness = 1.0 + progress * 0.3;
var enhancedColor = BrightenColor(block.Color, brightness);
block.Rectangle.Fill = new SolidColorBrush(Color.FromArgb(enhancedColor.A, enhancedColor.R, enhancedColor.G, enhancedColor.B));
}
else
{
// 非活动块:淡出效果
block.Rectangle.Opacity = Math.Max(0, 0.3 - progress * 0.25);
}
}
}
// 启动新的块
_currentBlockIndex = Math.Min(_currentBlockIndex + blocksPerFrame, _blockVisuals.Count - 1);
// 检查是否所有动画完成
bool allComplete = true;
foreach (var block in _blockVisuals)
{
if (block.AnimationProgress < 1.0)
{
allComplete = false;
break;
}
}
if (allComplete)
{
_animationTimer.Stop();
_isAnimating = false;
StartButton.Content = "重新开始";
}
}
演示:单射-非满射
如:星云映射/旋涡收缩
处理规则:
1,每个源像素映射到唯一位置(保持单射)
2,映射后的图像分散到目标图像局部,留下很多空白(违反满射)
视觉效果-星云映射:源像素点飞落到不同位置,呈现散落的星云状态。

部分代码:
private void SetupAnimationTimer()
{
_animationTimer = new DispatcherTimer();
_animationTimer.Interval = TimeSpan.FromMilliseconds(16); // 60 FPS
_animationTimer.Tick += AnimationTimer_Tick;
}
private void AnimationTimer_Tick(object sender, object e)
{
if (!_isAnimating) return;
var currentTime = DateTime.Now.TimeOfDay.TotalSeconds;
foreach (var pixel in _pixelMappings)
{
if (!pixel.IsActive)
{
if (currentTime - pixel.StartTime >= pixel.StartDelay)
{
ActivatePixel(pixel);
pixel.IsActive = true;
_mappedCount++;
UpdateStats();
}
continue;
}
if (pixel.Visual != null)
{
var elapsed = (float)(currentTime - pixel.StartTime - pixel.StartDelay);
var progress = Math.Min(elapsed * _animationSpeed / 1.5f, 1.0f);
if (progress < 1.0f)
{
// 动画轨迹
var sourcePos = new Vector2(
pixel.SourcePosition.X * CANVAS_SIZE / _sourceBitmap.Width,
pixel.SourcePosition.Y * CANVAS_SIZE / _sourceBitmap.Height);
var t = EaseInOutCubic(progress);
var currentPos = Vector2.Lerp(sourcePos, pixel.TargetPosition, t);
// 添加轻微的曲线效果
var curve = Math.Sin(t * Math.PI) * 30;
currentPos.Y -= (float)curve;
pixel.Visual.Offset = new Vector3(currentPos.X - pixel.Size/2, currentPos.Y - pixel.Size/2, 0);
pixel.Visual.Opacity = Math.Min(1.0f, progress * 2);
// 更新源高亮
if (pixel.SourceHighlight != null)
{
pixel.SourceHighlight.Opacity = 1.0f - progress;
}
}
else
{
// 到达目标位置
pixel.Visual.Offset = new Vector3(pixel.TargetPosition.X - pixel.Size/2, pixel.TargetPosition.Y - pixel.Size/2, 0);
pixel.Visual.Opacity = 0.7f + pixel.Brightness * 0.3f;
// 闪烁效果
var twinkle = 1.0f + (float)Math.Sin(elapsed * Math.PI * 2) * 0.05f;
pixel.Visual.Scale = new Vector3(twinkle, twinkle, 1.0f);
// 显示占用指示器
if (pixel.OccupancyIndicator != null && pixel.OccupancyIndicator.Opacity < 1)
{
pixel.OccupancyIndicator.Opacity = Math.Min(1.0f, pixel.OccupancyIndicator.Opacity + 0.1f);
}
}
}
}
// 更新星座连线
if (_currentMode == MappingMode.ScatteredConstellation)
{
UpdateConstellationLines();
}
// 检查是否完成
if (_pixelMappings.All(p => p.IsActive))
{
var allReached = true;
foreach (var pixel in _pixelMappings)
{
var elapsed = (float)(currentTime - pixel.StartTime - pixel.StartDelay);
var progress = elapsed * _animationSpeed / 1.5f;
if (progress < 1.0f)
{
allReached = false;
break;
}
}
if (allReached)
{
_isAnimating = false;
_animationTimer.Stop();
StartButton.Content = "开始单射映射";
ShowCompletionStats();
}
}
}
视觉效果-旋涡收缩:原图像发生漩涡状扭曲收缩,局部留有旋涡的缝隙。

部分代码:
private void CompositionTarget_Rendering(object sender, object e)
{
var elapsed = (DateTime.Now - _startTime).TotalSeconds;
var allSpritesFinished = true;
// 更新每个精灵的位置和颜色
for (int i = 0; i < _spriteVisuals.Count; i++)
{
var sprite = _spriteVisuals[i];
var info = sprite.Comment.Split(',');
var targetX = float.Parse(info[0]);
var targetY = float.Parse(info[1]);
var normalizedDistance = float.Parse(info[2]);
var sourceX = float.Parse(info[3]);
var sourceY = float.Parse(info[4]);
// 计算延迟 - 基于距离的波纹效果
var delay = normalizedDistance * 0.2;
var adjustedElapsed = elapsed - delay;
if (adjustedElapsed < 0)
{
// 如果还没到开始时间,保持起点位置和透明
sprite.Opacity = 0;
allSpritesFinished = false;
continue;
}
var progress = Math.Min(adjustedElapsed / _animationDuration.TotalSeconds, 1.0);
if (progress < 1.0)
{
allSpritesFinished = false;
}
// 使用更平滑的缓动函数
var easedProgress = EaseInOutQuart(progress);
// 计算当前位置(线性插值)
var currentX = sourceX + (targetX - sourceX) * easedProgress;
var currentY = sourceY + (targetY - sourceY) * easedProgress;
// 更新位置
sprite.Offset = new Vector3((float)(currentX - sprite.Size.X / 2), (float)(currentY - sprite.Size.Y / 2), 0);
// 更新透明度 - 添加淡入效果
sprite.Opacity = (float)easedProgress;
// 保持原始颜色,只做轻微的亮度调整
var colorProgress = easedProgress;
var brightness = 1.0f + colorProgress * 0.15f; // 非常轻微的亮度变化
var originalColor = (sprite.Brush as CompositionColorBrush).Color;
// 保持原图颜色特征
var newColor = Color.FromArgb(
(byte)(255 * easedProgress),
(byte)Math.Min(255, originalColor.R * brightness),
(byte)Math.Min(255, originalColor.G * brightness),
(byte)Math.Min(255, originalColor.B * brightness)
);
(sprite.Brush as CompositionColorBrush).Color = newColor;
}
if (allSpritesFinished)
{
CompositionTarget.Rendering -= CompositionTarget_Rendering;
_isAnimating = false;
StartButton.Content = "开始动画";
}
}
演示:非单射-满射
例如:网格覆盖效果。
处理规则:
1,多个源像素混合映射到同一目标像素(违反单射)
2,但覆盖整个目标图像(保持满射)
视觉效果: 原图像的细节,像是被风化腐蚀一般,细节损失,却结果完整。

部分代码:
private void AnimationTimer_Tick(object sender, object e)
{
if (!_isAnimating) return;
var currentTime = DateTime.Now.TimeOfDay.TotalSeconds;
// 批量激活像素以提高性能
int activatedThisFrame = 0;
while (_nextActivationIndex < _pixelMappings.Count && activatedThisFrame < _batchSize)
{
var mapping = _pixelMappings[_nextActivationIndex];
if (!mapping.IsActive && currentTime - mapping.StartTime >= mapping.StartDelay)
{
ActivateMapping(mapping);
mapping.IsActive = true;
_activePixels.Add(mapping);
activatedThisFrame++;
}
_nextActivationIndex++;
}
// 只更新活跃的像素
for (int i = _activePixels.Count - 1; i >= 0; i--)
{
var mapping = _activePixels[i];
if (mapping.Visual != null && !mapping.HasReachedTarget)
{
var elapsed = (float)(currentTime - mapping.StartTime - mapping.StartDelay);
var progress = Math.Min(elapsed * _animationSpeed / 0.8f, 1.0f); // 稍微加快动画速度
if (progress < 1.0f)
{
// 简化动画轨迹以提高性能
var sourcePos = new Vector2(
mapping.SourcePosition.X * 400f / _sourceBitmap.Width,
mapping.SourcePosition.Y * 400f / _sourceBitmap.Height);
var currentPos = Vector2.Lerp(sourcePos, mapping.TargetPosition, progress);
// 简化弧线计算
if (progress < 0.5f)
{
var arc = progress * 2 * 50; // 增加弧线高度
currentPos.Y -= arc;
}
else
{
var arc = (1 - progress) * 2 * 50; // 增加弧线高度
currentPos.Y -= arc;
}
mapping.Visual.Offset = new Vector3(currentPos.X, currentPos.Y, 0);
}
else
{
// 到达目标
mapping.Visual.Offset = new Vector3(mapping.TargetPosition.X, mapping.TargetPosition.Y, 0);
mapping.HasReachedTarget = true;
// 更新网格颜色
UpdateGridCell(mapping.TargetCell);
// 从活跃列表中移除
_activePixels.RemoveAt(i);
}
}
}
// 检查是否完成
if (_nextActivationIndex >= _pixelMappings.Count && _activePixels.Count == 0)
{
_isAnimating = false;
_animationTimer.Stop();
StartButton.Content = "开始覆盖";
StartButton.IsEnabled = true;
// 确保最终覆盖率显示正确
DispatcherQueue.TryEnqueue(() =>
{
var finalCoverage = (_filledCells * 100.0) / _totalCells;
CoverageText.Text = $"覆盖率: {finalCoverage:F1}% - 满射完成!";
CoverageProgressBar.Value = finalCoverage;
});
}
}
private void ActivateMapping(PixelMapping mapping)
{
var visual = _compositor.CreateSpriteVisual();
visual.Size = new Vector2(mapping.Size, mapping.Size);
visual.Brush = _compositor.CreateColorBrush(
Color.FromArgb(mapping.Color.A, mapping.Color.R, mapping.Color.G, mapping.Color.B));
// 设置初始位置
var startPos = new Vector2(
mapping.SourcePosition.X * 400f / _sourceBitmap.Width,
mapping.SourcePosition.Y * 400f / _sourceBitmap.Height);
visual.Offset = new Vector3(startPos.X, startPos.Y, 0);
// 设置较低的渲染优先级以提高性能
visual.IsPixelSnappingEnabled = true;
_containerVisual.Children.InsertAtTop(visual);
mapping.Visual = visual;
}
演示:单射-满射
即双射、即一一映射。 如:波浪变形效果
处理规则:
1,每个像素一一对应映射(保持单射)
2,覆盖整个目标图像(保持满射)
视觉效果:优雅的正弦波变形,保持图像完整性。

部分代码:
private void AnimationTimer_Tick(object sender, object e)
{
_totalAnimationTime += 0.016 * _animationSpeed; // 16ms per frame
bool allAnimationsComplete = true;
foreach (var pixelVisual in _pixelVisuals)
{
var elapsedTime = _totalAnimationTime - pixelVisual.Delay;
if (elapsedTime < 0)
{
// 还未开始动画
allAnimationsComplete = false;
continue;
}
// 动画持续时间
var animationDuration = 2.0;
var progress = Math.Min(elapsedTime / animationDuration, 1.0);
if (progress < 1.0)
{
allAnimationsComplete = false;
}
// 使用缓动函数
var easedProgress = EaseInOutSine((float)progress);
// 插值位置
var currentX = pixelVisual.SourcePosition.X + (pixelVisual.TargetPosition.X - pixelVisual.SourcePosition.X) * easedProgress;
var currentY = pixelVisual.SourcePosition.Y + (pixelVisual.TargetPosition.Y - pixelVisual.SourcePosition.Y) * easedProgress;
// 添加动态波浪效果
if (progress > 0.5 && progress < 1.0)
{
float wavePhase = (float)(_totalAnimationTime * 2);
float dynamicWave = (float)(Math.Sin(wavePhase + pixelVisual.Col * 0.1) * 5 * (1 - progress));
currentY += dynamicWave;
}
pixelVisual.Visual.Offset = new Vector3(currentX, currentY, 0);
// 添加轻微的旋转效果
if (progress > 0.3 && progress < 0.7)
{
float rotation = (float)Math.Sin(progress * Math.PI) * 0.1f;
pixelVisual.Visual.RotationAngleInDegrees = rotation * 10;
}
else
{
pixelVisual.Visual.RotationAngleInDegrees = 0;
}
}
if (allAnimationsComplete && _isAnimating)
{
StopAnimation();
}
}
private float EaseInOutSine(float t)
{
return -(float)(Math.Cos(Math.PI * t) - 1) / 2;
}
无演示:逆映射
只有一一映射(双射)才有逆映射 ,逆映射就是把输入输出对调,反向的过程。因此,双射具有可还原性。以上个例子来说,即把扭曲后的图像,还原为原始图像的过程,不再演示。
数学,可以很美,很浪漫 ~

数学,可以很美,很浪漫。用C#+WinUI3+图像处理动画 可视化理解映射:单射、满射、双射。
浙公网安备 33010602011771号