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

准确-单-非满-星云映射2
封面


什么是 映射

映射,是现代数学中的一个基本概念 ,包含单射、满射、双射、逆映射、复合映射。

单射:不同的输入映射到不同的输出;
满射:每个可能的输出都有至少一个输入映射到它;
双射:既是单射又是满射;
逆映射:只有一一对应的映射才有逆映射 ,即输入输出对调;
复合映射:把多个映射"串起来",先做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,映射后的图像分散到目标图像局部,留下很多空白(违反满射)

视觉效果-星云映射:源像素点飞落到不同位置,呈现散落的星云状态。

准确-单-非满-星云映射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;
}

无演示:逆映射

只有一一映射(双射)才有逆映射 ,逆映射就是把输入输出对调,反向的过程。因此,双射具有可还原性。以上个例子来说,即把扭曲后的图像,还原为原始图像的过程,不再演示。

数学,可以很美,很浪漫 ~

posted @ 2025-07-19 22:08  行人--  阅读(342)  评论(0)    收藏  举报