打印
[应用相关]

STM32 ICO图标解析(支持透明度)

[复制链接]
934|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
测试环境为STM32F7,支持2个图层,可以支持图层之间显示透明度,测试方法为底层显示背景图标,顶层显示需要显示透明的图标,如果在单个图层显示,需要自己实现alpha混合,混合需要读取之前的像素,与要写入的像素进行混合,混合方法也比较简单可以网上查


准备工作,由于ico图标种类很多,目前解析的是最简单的,ARGB位图格式bmp,默认解析ico中第一个图标(ico中支持多个图标文件),可以使用 IconWorkshop这个软件将透明的png转换为ico,需要选择RGBA格式,并且不要压缩.




使用特权

评论回复
沙发
八层楼|  楼主 | 2021-8-1 17:02 | 只看该作者
//解析代码如下



/*************************************************************************************************************
* 文件名                :        IcoDecode.c
* 功能                        :        ICO图片软件解码
* 作者                        :        cp1300@139.com
* 创建时间                :        2020-03-11
* 最后修改时间        :        2020-03-11
* 详细                        :        只支持32bit ARGB非压缩的ICO图片解析,ICO内部与bmp类似,从左下角开始刷新
*************************************************************************************************************/
#include "system.h"
#include "IcoDecode.h"
#include "PicDecode.h"



//检查是否需要分段加载文件(当开启了分段加载,并且当前解析位置到达了已经读取的文件结尾或超出了,则重新加载一包数据)
#define CheckisStageLoad()   ((pDecodeTempData->isStageLoadFile) && (pDecodeTempData->FileOffset >= (pDecodeTempData->ThisReadCount + pDecodeTempData->ThisReadStartOffset)))


//一个ICO文件中的某一个图标信息体
#ifdef WIN32
#pragma pack (1)                //强制使用字节对齐
typedef  struct        //__packed:让结构体的各个元素紧挨着存储
#else //MDK
typedef  __packed  struct
#endif //WIM32
{
    u8 bWidth;        //图像宽度,单位:像素
    u8 bHeight;       //图像高度,单位:像素
    u8 bColorCount;   //颜色数
    u8 bReserved;     //保留,为0
    u16 wPlanes;       //平面数,一般为1
    u16 wBitCount;     //每像素比特数
    u32 dwBytesInRes; //数据块大小
    u32 dwImageOffset;//数据块偏移量
}ICO_ONE_INFO;
#ifdef WIN32
#pragma pack() //取消自定义字节对齐方式。
#endif //WIN32

//一个ICO文件中的某一个图标信息体
#ifdef WIN32
#pragma pack (1)                //强制使用字节对齐
typedef  struct        //__packed:让结构体的各个元素紧挨着存储
#else //MDK
typedef  __packed  struct
#endif //WIM32
{
    u16 idReserved;             //保留,为0
    u16 idType;                 //文件类型,图标为1,光标为2
    u16 idCount;                //图象个数
    ICO_ONE_INFO IcoInfoBuff[1];
}ICO_FILE_HEADER;
#ifdef WIN32
#pragma pack() //取消自定义字节对齐方式。
#endif //WIN32



//32位BMP文件头部信息结构-ico图标内也有bmp头
#ifdef WIN32
#pragma pack (1)                //强制使用字节对齐
typedef  struct        //__packed:让结构体的各个元素紧挨着存储
#else //MDK
typedef  __packed  struct
#endif //WIM32
{
    u32 bmfHeaderSize;                //图像描述信息块的大小
    int biWidth;                         //说明图象的宽度,以象素为单位
    int biHeight;                         //说明图象的高度,以象素为单位(XOR图高度+AND图高度)
    u16 biPlanes;                         //为目标设备说明位面数,其值将总是被设为1
    u16 biBitCount;                 //说明比特数/象素,其值为1、4、8、16、24、或32,目前只支持32bit
    u32 biCompression;             //说明图象数据压缩的类型。必须为0
    u32 biSizeImage;                //说明图象的大小,以字节为单位
    u32 bfReserved1[4];     //保留
}ICO_BMP_HEADER;
#ifdef WIN32
#pragma pack() //取消自定义字节对齐方式。
#endif //WIN32

//ICO图片解析所需的变量数据集合
typedef struct
{
    ICO_INFO mIcoInfo;                                        //图片信息
    ICO_Decode_Parm* pDecodeParm;                //图片解析输入的参数
    u32 OneReadCount;                                        //一次读取文件大小限制
    u32 ThisReadStartOffset;                        //当前一次读取的文件开始偏移(相对于整个文件的偏移)
    u32 ThisReadCount;
    u32* InData32bit;                                        //临时指针,用于读取32BIT的图片颜色值-指向的是原始图片缓冲区
    u32 FileOffset;                                                //文件偏移,用于判断是否超出文件范围了
    u16 xSaveValid;                                                  //x轴方向实际存储的像素数据字节数
    u16 IcoY;                                                        //Ico图片像素Y坐标-不是LCD的屏幕坐标,是当前显示的Ico位置坐标
    u16 xPos, yPos;                                                //屏幕坐标-屏幕画点坐标
    float ScaleFactor;                                        //图片收缩系数,1:不收缩;<1将缩小,缩小是会保证宽高比不变
    float FactorCumulaX;                                //X方向缩放累加计数器,浮点型,每当增1后显示当前列
    float FactorCumulaY;                                //Y方向缩放累加计数器,浮点型,每当增1后显示当前行
    u16 LastFactorCumulaIntX;                        //X方向缩放累加计数器,整形数,用于记录上一次的值,用于对比是否发生了增量(+1),来自于FactorCumulaX的整数部分
    u16 LastFactorCumulaIntY;                        //Y方向缩放累加计数器,整形数,用于记录上一次的值,用于对比是否发生了增量(+1),来自于FactorCumulaY的整数部分
    u16 offsetX, offsetY;                                //用于图片居中显示时,X,Y的偏移
    u16 CachePixelCount;                                //缓存的像素数量
    u16 CachePixelSize;                 //缓存的像素缓冲区大小,像素大小
    u16 FillPixelXpos;                  //填充图形的像素X起始坐标
        u8 OutPixelByte;                                        //输出像素字节数,根据输出的像素格式决定,可以是2,3,4,对应RGB565,RGB888,ARGB8888
    bool isZoom;                                                //是否需要缩放
    bool isDisplayThisPixel;                        //是否显示当前像素,TRUE:显示,FALSE:不显示,用于跳过一些像素,达到缩小图片的目的
    bool isStageLoadFile;                                //用于指示后面的图片解析函数,是否需要分段加载文件(2种情况下不需要,1:图片文件已经提前加载完成了,2:图片文件小于缓冲区大小,一次加载完成了)
}ICO_DecodeDataType;

static ICO_ERROR ICO_Decode32bit_Module(ICO_DecodeDataType* pDecodeTempData);//子流程-32bit ico位图解析(从内存中读取图片数据,然后解析到内存中)



使用特权

评论回复
板凳
八层楼|  楼主 | 2021-8-1 17:03 | 只看该作者
/*************************************************************************************************************************
* 函数                        :        ICO_ERROR ICO_DecodeZoom_LoadFileData(ICO_DecodeDataType *pDecodeTempData)
* 功能                        :        子流程-ICO解析:加载文件数据
* 参数                        :        pDecodeTempData:解析所需的变量;
* 返回                        :        ICO_ERROR
* 依赖                        :        底层
* 作者                        :        cp1300@139.com
* 时间                        :        2020-02-18
* 最后修改时间         :         2020-03-12
* 说明                        :         请先调用CheckisStageLoad() 进行判断是否执行本函数
*************************************************************************************************************************/
ICO_ERROR ICO_DecodeZoom_LoadFileData(ICO_DecodeDataType* pDecodeTempData)
{
    int ThisReadOffset;                //记录当前读取时使用的偏移

    if (pDecodeTempData->FileOffset > (pDecodeTempData->ThisReadCount + pDecodeTempData->ThisReadStartOffset))
    {
        ThisReadOffset = pDecodeTempData->FileOffset;                //超出了文件范围,需要跳过一些字节,重新设置文件读偏移
    }
    else
    {
        ThisReadOffset = -1;                                                                //不需要偏移,接着之前的读取
    }
    pDecodeTempData->ThisReadStartOffset = pDecodeTempData->FileOffset; //记录新的读取位置偏移//pDecodeTempData->ThisReadCount;                //当前数据偏移,加上上次读取的数据长度
    //读取一包数据-接着读取,不需要偏移
    if (pDecodeTempData->pDecodeParm->Func_ReadFile(pDecodeTempData->pDecodeParm->pInFileHandle, ThisReadOffset, pDecodeTempData->pDecodeParm->InFileBuff, pDecodeTempData->OneReadCount,
        &pDecodeTempData->ThisReadCount) == FALSE)
    {
        DEBUG("读取ICO图片文件失败\r\n");

        return ICO_FILE_ERROR;
    }
    //uart_printf("FileOffset:%d(%d)\r\n", pDecodeTempData->FileOffset, pDecodeTempData->ThisReadCount);

    //读取一包数据,初始化相关信息       
    pDecodeTempData->InData32bit = (u32*)&pDecodeTempData->pDecodeParm->InFileBuff[0];                                //跳到图片数据区-图片是分段加载的,起点就是正文处

    return ICO_OK;
}

/*************************************************************************************************************************
* 函数                        :        ICO_ERROR ICO_DecodeZoom_NextValidRow(ICO_DecodeDataType* pDecodeTempData)
* 功能                        :        子流程-ICO解析跳转到下一个能被显示的行(用于缩放)
* 参数                        :        pDecodeTempData:解析所需的变量;
* 返回                        :        ICO_ERROR
* 依赖                        :        底层
* 作者                        :        cp1300@139.com
* 时间                        :        2020-02-07
* 最后修改时间         :         2020-03-12
* 说明                        :         无
*************************************************************************************************************************/
ICO_ERROR ICO_DecodeZoom_NextValidRow(ICO_DecodeDataType* pDecodeTempData)
{
    u8* pData;

    //需要缩放,那就先找到下一个能显示的行
    while (pDecodeTempData->FileOffset < pDecodeTempData->pDecodeParm->InFileSize)
    {
        pDecodeTempData->FactorCumulaY += pDecodeTempData->ScaleFactor;                                                                //缩放系数累加,每当+1后就能被显示
        if ((u16)pDecodeTempData->FactorCumulaY > pDecodeTempData->LastFactorCumulaIntY)                        //发生增1了
        {
            pDecodeTempData->LastFactorCumulaIntY = (u16)pDecodeTempData->FactorCumulaY;                        //记录
            break;                                                                                                                                                                        //退出循环
        }

        //跳过一行 32bit像素
        pDecodeTempData->InData32bit += pDecodeTempData->mIcoInfo.biWidth;                                                        //文件指针自增,直接跳过一行
        pDecodeTempData->FileOffset += 4 * pDecodeTempData->mIcoInfo.biWidth;                                                //文件偏移
    }

    if (CheckisStageLoad())                                        //需要分段加载文件
    {
        ICO_DecodeZoom_LoadFileData(pDecodeTempData);//子流程-ICO解析:加载文件数据
    }

    return ICO_OK;
}



使用特权

评论回复
地板
八层楼|  楼主 | 2021-8-1 17:04 | 只看该作者
/*************************************************************************************************************************
* 函数                        :        ICO_ERROR ICO_Decode(ICO_Decode_Parm* pDecodeParm)
* 功能                        :        ICO图片软解析(全部解析)
* 参数                        :        pDecodeParm:相关参数(见ICO_Decode_Parm)
* 返回                        :        ICO_ERROR
* 依赖                        :        底层
* 作者                        :        cp1300@139.com
* 时间                        :        2011-09-19
* 最后修改时间         :         2020-02-19
* 说明                        :         输入缓冲区最小要给128字节
*************************************************************************************************************************/
ICO_ERROR ICO_Decode(ICO_Decode_Parm* pDecodeParm)
{
        ICO_FILE_HEADER* pIcoHeader;                                //图片文件头
        ICO_DecodeDataType mDecodeTempData;                        //图片解析所需的临时变量
        ICO_ERROR mIcoError = ICO_OK;                                //图片解析状态
        u32 temp;
        float ftemp;
        u32 ActualReadCount;
    ICO_BMP_HEADER* pICO_BmpHeader;

        if (pDecodeParm == NULL || pDecodeParm->InFileBuff == NULL || pDecodeParm->InFileBuffSize < 128 || pDecodeParm->InFileSize == 0
                || ((pDecodeParm->isLoadFile == TRUE) && pDecodeParm->Func_ReadFile == NULL) || ((pDecodeParm->isLoadFile == TRUE) && pDecodeParm->pInFileHandle == NULL)
                || pDecodeParm->Func_FillPoint == NULL)
        {
                DEBUG("ICO解析失败:无效的参数(请提供正确的:pDecodeParm,缓冲区,缓冲区大小,文件大小,读取文件回调与文件句柄(前提是使能了文件加载),画点接口)\r\n");
                return ICO_PARM_ERROR;
        }
        if (pDecodeParm->isLoadFile == TRUE)                                                                                        //需要加载文件
        {
                if (pDecodeParm->InFileBuffSize >= pDecodeParm->InFileSize)                                        //缓冲区比文件大,可以一次加载完文件的所有数据
                {
                        mDecodeTempData.ThisReadCount = pDecodeParm->InFileSize;                                //本次要读取的数据大小
                        mDecodeTempData.isStageLoadFile = FALSE;                                                                //图片文件一次加载完成了,不需要再加载了
                }
                else
                {
                        mDecodeTempData.ThisReadCount = 128;                                                                        //本次要读取的数据大小
                        mDecodeTempData.isStageLoadFile = TRUE;                                                                        //需要分段加载文件
                }

                if (pDecodeParm->Func_ReadFile(pDecodeParm->pInFileHandle, 0, pDecodeParm->InFileBuff, mDecodeTempData.ThisReadCount, &ActualReadCount) == FALSE)
                {
                        DEBUG("ICO解析失败:加载文件失败\r\n");
                        return ICO_FILE_ERROR;        //文件加载失败
                }
                else if (mDecodeTempData.ThisReadCount != ActualReadCount)
                {
                        DEBUG("ICO解析失败:文件加载不全\r\n");
                        return ICO_FILE_ERROR;        //文件加载失败
                }
        }
        else
        {
                mDecodeTempData.isStageLoadFile = FALSE;                                                                        //图片文件已经提前加载了,不需要再加载了
        }

        //基本数据初始化
        pIcoHeader = (ICO_FILE_HEADER*)pDecodeParm->InFileBuff;                                                        //得到ICO的头部信息
        if (pIcoHeader->idType != 1)
        {
        DEBUG("ICO解析失败:不是有效的ICO图标文件\r\n");
        return ICO_ILLEGAL_ERROR;        //不支持
        }
        if (pIcoHeader->idCount == 0)
        {
        DEBUG("ICO解析失败:图标数量为0\r\n");
        return ICO_ILLEGAL_ERROR;        //不支持
        }
        if (pIcoHeader->IcoInfoBuff[0].wBitCount != 32)
        {
        DEBUG("ICO解析失败:只支持32bit的非压缩ARGB图标解析\r\n");
        return ICO_ILLEGAL_ERROR;        //不支持
        }

        mDecodeTempData.mIcoInfo.bfOffBits = pIcoHeader->IcoInfoBuff[0].dwImageOffset;                         //位图数据偏移地址偏移
    pICO_BmpHeader = (ICO_BMP_HEADER*)&pDecodeParm->InFileBuff[mDecodeTempData.mIcoInfo.bfOffBits]; //获取bmp头信息
    /****************************************************/
    //调试               
    uart_printf("\r\n");
    uart_printf("位图大小:%d\r\n", pICO_BmpHeader->bmfHeaderSize);
    uart_printf("颜色深度:%d\r\n", pICO_BmpHeader->biBitCount);
    uart_printf("水平分辨率:%d\r\n", pICO_BmpHeader->biWidth);
    uart_printf("垂直分辨率:%d\r\n", pICO_BmpHeader->biHeight);
    uart_printf("数据大小:%luB\r\n", pICO_BmpHeader->biSizeImage);
    uart_printf("位图压缩:%d\r\n", pICO_BmpHeader->biCompression);
    /*****************************************************/
    if (pICO_BmpHeader->bmfHeaderSize != 40 || pICO_BmpHeader->biCompression != 0 || pICO_BmpHeader->biBitCount != 32)
    {
        DEBUG("ICO解析失败:只支持32bit的非压缩ARGB图标解析(位图信息错误)\r\n");
        return ICO_ILLEGAL_ERROR;        //不支持
    }
    mDecodeTempData.mIcoInfo.biSizeImage = pICO_BmpHeader->biSizeImage;             //图像数据大小
    mDecodeTempData.mIcoInfo.biWidth = pICO_BmpHeader->biWidth;                     //图片宽度
    mDecodeTempData.mIcoInfo.biHeight = pICO_BmpHeader->biHeight/2;                                        //图片高度,位图的高度翻倍了
        mDecodeTempData.mIcoInfo.biBitCount = pICO_BmpHeader->biBitCount;                                //颜色深度

       
        mDecodeTempData.pDecodeParm = pDecodeParm;                                                                                                //记录输入的参数
        mDecodeTempData.ThisReadCount = 0;                                                                                                                //此次读取的文件大小复位
        mDecodeTempData.ThisReadStartOffset = 0;                                                                                                //此次读取的文件开始偏移复位-相对于整个文件内的偏移       
        /****************************************************/
        //调试                                                                                               
        uart_printf("\r\n地址偏移:%d\r\n", mDecodeTempData.mIcoInfo.bfOffBits);
        uart_printf("颜色深度:%d\r\n", mDecodeTempData.mIcoInfo.biBitCount);
        uart_printf("水平分辨率:%d\r\n", mDecodeTempData.mIcoInfo.biWidth);
        uart_printf("垂直分辨率:%d\r\n", mDecodeTempData.mIcoInfo.biHeight);
        uart_printf("数据大小:%luB\r\n", mDecodeTempData.mIcoInfo.biSizeImage);
        uart_printf("图像标志:0x%04X\r\n", pIcoHeader->idType);
        uart_printf("图形数量:%d\r\n\r\n", pIcoHeader->idCount);
        /*****************************************************/

        //计算缩放比例
        ftemp = pDecodeParm->OutRGB_ImageWidth;
        ftemp /= (float)mDecodeTempData.mIcoInfo.biWidth;                                                                                                                    //水平缩放率
        if (ftemp > 1) ftemp = 1;                                                                                                                                                                        //只能缩小,不能放大
        mDecodeTempData.ScaleFactor = pDecodeParm->OutRGB_ImageHeight;
        mDecodeTempData.ScaleFactor /= (float)mDecodeTempData.mIcoInfo.biHeight;                                                                    //垂直缩放率
        if (mDecodeTempData.ScaleFactor > 1) mDecodeTempData.ScaleFactor = 1;                                                                                //只能缩小,不能放大
        if (ftemp < mDecodeTempData.ScaleFactor) //水平缩放比例大,会导致垂直方向无法填充满
        {
                mDecodeTempData.offsetX = 0;
                mDecodeTempData.offsetY = (pDecodeParm->OutRGB_ImageHeight - (ftemp * mDecodeTempData.mIcoInfo.biHeight)) / 2;                //计算居中坐标
                mDecodeTempData.ScaleFactor = ftemp;                                                                                                                                        //使用较小的那个缩放率进行缩放
        }
        else //垂直方向缩放比例大,会导致垂直方向无法填充满
        {
                mDecodeTempData.offsetY = 0;
                mDecodeTempData.offsetX = (pDecodeParm->OutRGB_ImageWidth - (mDecodeTempData.ScaleFactor * mDecodeTempData.mIcoInfo.biWidth)) / 2;                //计算居中坐标                                                                                                                        //使用较小的那个缩放率进行缩放
        }

        uart_printf("图片缩放率:%f\r\n", mDecodeTempData.ScaleFactor);
        mDecodeTempData.FactorCumulaX = 0.0f;                                                                                                                                                //X方向缩放累加计数器,浮点型,每当增1后显示当前列
        mDecodeTempData.FactorCumulaY = 0.0f;                                                                                                                                                //Y方向缩放累加计数器,浮点型,每当增1后显示当前行
        mDecodeTempData.LastFactorCumulaIntX = 0;                                                                                                                                        //X方向缩放累加计数器,整形数,用于记录上一次的值,用于对比是否发生了增量(+1)
        mDecodeTempData.LastFactorCumulaIntY = 0;                                                                                                                                        //Y方向缩放累加计数器,整形数,用于记录上一次的值,用于对比是否发生了增量(+1)
        if (mDecodeTempData.ScaleFactor != 1.0f)
        {
                mDecodeTempData.isZoom = TRUE;                                                                                                                                                        //需要缩放
                mDecodeTempData.isDisplayThisPixel = FALSE;                                                                                                                                //当前行列不能显示
        }
        else
        {
                mDecodeTempData.isZoom = FALSE;                                                                                                                                                        //不需要缩放
                mDecodeTempData.isDisplayThisPixel = TRUE;                                                                                                                                //当前行列能显示
        }

        //初始化坐标-坐标显示范围由图片的真实宽度高度*缩放系数得到,ico从左下角开始解析
        mDecodeTempData.IcoY = mDecodeTempData.mIcoInfo.biHeight * mDecodeTempData.ScaleFactor - 1;                                        //ICO解析计数,变为0后解析完成

        //输入指针初始化
        mDecodeTempData.FileOffset = mDecodeTempData.mIcoInfo.bfOffBits + pICO_BmpHeader->bmfHeaderSize;                        //文件偏移(位图偏移加位图信息块大小)
        if (mDecodeTempData.isStageLoadFile == FALSE)                                                                                                                                //不需要分段加载,直接解析内存中的数据
        {
                mDecodeTempData.InData32bit = (u32*)&pDecodeParm->InFileBuff[mDecodeTempData.FileOffset];                                //跳到图片数据区
        }
        else //需要分包加载图片,计算一次读取的数据数量
        {
        mDecodeTempData.OneReadCount = pDecodeParm->InFileBuffSize / 4;
        mDecodeTempData.OneReadCount *= 4;
                mDecodeTempData.ThisReadStartOffset = mDecodeTempData.mIcoInfo.bfOffBits + pICO_BmpHeader->bmfHeaderSize;                        //文件偏移(位图偏移加位图信息块大小)
                //读取第一包数据
                if (pDecodeParm->Func_ReadFile(pDecodeParm->pInFileHandle, mDecodeTempData.ThisReadStartOffset+0, pDecodeParm->InFileBuff,
                        mDecodeTempData.OneReadCount, &mDecodeTempData.ThisReadCount) == FALSE)
                {
                        mIcoError = ICO_FILE_ERROR;
                        DEBUG("读取ICO图片文件失败\r\n");
                        goto end_loop;
                }
                //读取的第一包数据,初始化相关信息               
                mDecodeTempData.InData32bit = (u32*)&pDecodeParm->InFileBuff[0];                        //跳到图片数据区-图片是分段加载的,起点就是正文处
        }
        //输出像素
        switch (pDecodeParm->OutRGB_ColorMode)                //输出像素数据格式
        {
                case PIC_COLOR_ARGB8888:        //32bit格式输出
                {
                        mDecodeTempData.OutPixelByte = 4;                //4字节
                }break;
                case PIC_COLOR_RGB888:        //24bit格式输出
                {
                        mDecodeTempData.OutPixelByte = 3;                //3字节
                }break;
                default:                                        //默认为RGB555
                {
                        mDecodeTempData.OutPixelByte = 2;                //2字节
                }break;
        }


        //需要缩放,那就先找到下一个能显示的行
        if (mDecodeTempData.isZoom)        //需要缩放
        {
                mIcoError = ICO_DecodeZoom_NextValidRow(&mDecodeTempData);                //子流程-ICO解析跳转到下一个能被显示的行(用于缩放)
                if (mIcoError != ICO_OK)
                {
                        goto end_loop;
                }
        }

        //计算画点坐标初值, 让图片居中显示
        mDecodeTempData.xPos = pDecodeParm->OutRGB_PixelOffsetX + mDecodeTempData.offsetX;                                                                //屏幕X坐标起始位置
        //Y坐标偏移从左下角开始
        mDecodeTempData.yPos = mDecodeTempData.IcoY + pDecodeParm->OutRGB_PixelOffsetY + mDecodeTempData.offsetY;

    mDecodeTempData.CachePixelSize = pDecodeParm->DecodeBuffSize / mDecodeTempData.OutPixelByte;       //计算像素缓冲区大小-像素大小
    mDecodeTempData.CachePixelCount = 0;                                //缓存的像素计数器清零
    mDecodeTempData.FillPixelXpos = mDecodeTempData.xPos;               //填充图形开始坐标X值


        //只支持32bit 非压缩ico解析
        mIcoError = ICO_Decode32bit_Module(&mDecodeTempData);        //子流程-32bit位图解析(从内存中读取图片数据,然后解析到内存中)
       

end_loop:
        return mIcoError;
}


使用特权

评论回复
5
八层楼|  楼主 | 2021-8-1 17:05 | 只看该作者
/*************************************************************************************************************************
* 函数                        :        void ICO_DecodeZoom_NextValidColumn(ICO_DecodeDataType *pDecodeTempData)
* 功能                        :        子流程-ico位图解析跳转到下一个能被显示的列(用于缩放)
* 参数                        :        pDecodeTempData:解析所需的变量;
* 返回                        :        ICO_ERROR
* 依赖                        :        底层
* 作者                        :        cp1300@139.com
* 时间                        :        2020-02-07
* 最后修改时间         :         2020-03-12
* 说明                        :         在外部判断 if(pDecodeTempData->isZoom == TRUE)        才调用此函数
*************************************************************************************************************************/
__inline void ICO_DecodeZoom_NextValidColumn(ICO_DecodeDataType* pDecodeTempData)
{
    //if(pDecodeTempData->isZoom == TRUE)                        //需要缩放
    {
        pDecodeTempData->FactorCumulaX += pDecodeTempData->ScaleFactor;                                                //缩放系数累加,每当+1后就能被显示
        if ((u16)pDecodeTempData->FactorCumulaX > pDecodeTempData->LastFactorCumulaIntX)        //发生增1了
        {
            pDecodeTempData->LastFactorCumulaIntX = (u16)pDecodeTempData->FactorCumulaX;        //记录
            pDecodeTempData->isDisplayThisPixel = TRUE;                                                                                //需要显示
        }
        else
        {
            pDecodeTempData->isDisplayThisPixel = FALSE;                                                                        //不需要显示
        }
    }
}


//RGB数据拷贝(系统自带的memcpy在某些对齐的情况下可能会导致程序崩溃)
static void ICO_memcpy(u8* destin, u8* source, u32 n)
{
    while (n--)
    {
        *destin = *source;
        destin++;
        source++;
    }
}



使用特权

评论回复
6
八层楼|  楼主 | 2021-8-1 17:06 | 只看该作者
/*************************************************************************************************************************
* 函数                        :        static ICO_ERROR ICO_Decode32bit_Module(ICO_DecodeDataType *pDecodeTempData)
* 功能                        :        子流程-32bit ico位图解析(从内存中读取图片数据,然后解析到内存中)
* 参数                        :        pDecodeTempData:解析所需的变量;
* 返回                        :        ICO_ERROR
* 依赖                        :        底层
* 作者                        :        cp1300@139.com
* 时间                        :        2020-03-12
* 最后修改时间         :         2020-03-12
* 说明                        :        
*************************************************************************************************************************/
static ICO_ERROR ICO_Decode32bit_Module(ICO_DecodeDataType* pDecodeTempData)
{
    ICO_ERROR mIcoError = ICO_OK;                                                                                        //图片解析状态
    u16 Xcnt = 0;                                                                                                                        //水平像素点计数(图片的实际水平分辨率计数)
    u16 Colro_RGB565;

    do
    {
        if (pDecodeTempData->isZoom == TRUE)        ICO_DecodeZoom_NextValidColumn(pDecodeTempData);                                        //子流程-ICO位图解析跳转到下一个能被显示的列(用于缩放)

        if (pDecodeTempData->isDisplayThisPixel == TRUE)                                        //需要显示
        {
            switch (pDecodeTempData->pDecodeParm->OutRGB_ColorMode)                        //输出像素数据格式
            {
                case PIC_COLOR_ARGB8888:                                                                        //32bit格式输出-注意:ARGB格式,支持透明度
                {
                    ICO_memcpy(&pDecodeTempData->pDecodeParm->DecodeBuff[pDecodeTempData->CachePixelCount * pDecodeTempData->OutPixelByte],
                        (u8 *)pDecodeTempData->InData32bit, pDecodeTempData->OutPixelByte);
                    //pDecodeTempData->pDecodeParm->Func_DrawPoint(pDecodeTempData->pDecodeParm->pGramHandle, pDecodeTempData->xPos, pDecodeTempData->yPos, *pDecodeTempData->InData32bit);
                }break;
                case PIC_COLOR_RGB888:                                                                            //24bit格式输出,不支持透明度
                {
                    ICO_memcpy(&pDecodeTempData->pDecodeParm->DecodeBuff[pDecodeTempData->CachePixelCount * pDecodeTempData->OutPixelByte],
                        (u8 *)pDecodeTempData->InData32bit, pDecodeTempData->OutPixelByte);

                    //ICO_memcpy((u8*)(&Color), (u8*)pDecodeTempData->InData32bit, 3);
                    //pDecodeTempData->pDecodeParm->Func_DrawPoint(pDecodeTempData->pDecodeParm->pGramHandle, pDecodeTempData->xPos, pDecodeTempData->yPos, Color);
                }break;
                default:                                                                                                        //默认为RGB565,不支持透明度
                {
                    Colro_RGB565 = RGB565(*pDecodeTempData->InData32bit);
                    ICO_memcpy(&pDecodeTempData->pDecodeParm->DecodeBuff[pDecodeTempData->CachePixelCount * pDecodeTempData->OutPixelByte],
                        (u8*)&Colro_RGB565, pDecodeTempData->OutPixelByte);
                    //pDecodeTempData->pDecodeParm->Func_DrawPoint(pDecodeTempData->pDecodeParm->pGramHandle, pDecodeTempData->xPos, pDecodeTempData->yPos, RGB565(*pDecodeTempData->InData32bit));
                }break;
            }
            pDecodeTempData->xPos++;                                                                                //画点水平位置增加
            pDecodeTempData->CachePixelCount++;                             //缓冲的像素计数器增加
        }

        Xcnt++;                                                                                                                                //bmp图片水平像素计数器

        pDecodeTempData->InData32bit++;
        pDecodeTempData->FileOffset += 4;                                                                                //文件偏移

        if (pDecodeTempData->CachePixelCount == (pDecodeTempData->CachePixelSize - 1)) //需要批量刷新
        {
            pDecodeTempData->pDecodeParm->Func_FillPoint(pDecodeTempData->pDecodeParm->pGramHandle, pDecodeTempData->FillPixelXpos,
                pDecodeTempData->yPos, pDecodeTempData->pDecodeParm->DecodeBuff, pDecodeTempData->CachePixelCount, 1);
            pDecodeTempData->CachePixelCount = 0;   //计数器清零
            pDecodeTempData->FillPixelXpos = pDecodeTempData->xPos;
        }


        if (Xcnt == pDecodeTempData->mIcoInfo.biWidth)                                                        //换行
        {
            pDecodeTempData->xPos = pDecodeTempData->pDecodeParm->OutRGB_PixelOffsetX + pDecodeTempData->offsetX;        //屏幕X坐标起始位置复位
            //换行了也要重新刷新一下
            if (pDecodeTempData->CachePixelCount > 0) //需要批量刷新
            {
                pDecodeTempData->pDecodeParm->Func_FillPoint(pDecodeTempData->pDecodeParm->pGramHandle, pDecodeTempData->FillPixelXpos,
                    pDecodeTempData->yPos, pDecodeTempData->pDecodeParm->DecodeBuff, pDecodeTempData->CachePixelCount, 1);
                pDecodeTempData->CachePixelCount = 0;   //计数器清零
            }
            pDecodeTempData->FillPixelXpos = pDecodeTempData->xPos;



            Xcnt = 0;                                                                                                                        //水平计数器清零
            //uart_printf("换行:%d\r\n", pDecodeTempData->y);
            pDecodeTempData->FactorCumulaX = 0;                                                                        //X方向缩放累加计数器复位
            pDecodeTempData->LastFactorCumulaIntX = 0;
            if (pDecodeTempData->IcoY == 0)                                                                         //结束了
            {
                uart_printf("图片解析结束\r\n");
                break;
            }



            pDecodeTempData->yPos -=  1;                                                                                //屏幕Y坐标更新                                       
            pDecodeTempData->IcoY--;
            //需要缩放,找到下一个能被显示的y,这个是针对输入的文件行,不是输出的y
            if (pDecodeTempData->isZoom == TRUE)
            {
                mIcoError = ICO_DecodeZoom_NextValidRow(pDecodeTempData);                //子流程-bmp解析跳转到下一个能被显示的行(用于缩放)
                if (mIcoError != ICO_OK)
                {
                    break;
                }
            }
        }

        //2020-02-18 增加包加载文件支持
        if (CheckisStageLoad())                                        //需要分段加载文件
        {
            ICO_DecodeZoom_LoadFileData(pDecodeTempData);//子流程-bmp解析:加载文件数据
        }
    } while (pDecodeTempData->FileOffset < pDecodeTempData->pDecodeParm->InFileSize);

    return mIcoError;
}


使用特权

评论回复
7
八层楼|  楼主 | 2021-8-1 17:06 | 只看该作者
/*************************************************************************************************************
* 文件名                :        IcoDecode.h
* 功能                        :        ICO图片软件解码
* 作者                        :        cp1300@139.com
* 创建时间                :        2020-03-11
* 最后修改时间        :        2020-03-11
* 详细                        :        只支持32bit ARGB非压缩的ICO图片解析
*************************************************************************************************************/
#ifndef __ICO_DECODE_H__
#define __ICO_DECODE_H__
#ifdef __cplusplus
extern "C"
{
#endif
#include "system.h"
#include "PicDecode.h"

//软解码ICO状态
typedef enum
{
        ICO_OK                                         = 0,        //解码成功
        ICO_ILLEGAL_ERROR                = 1,        //非法的图片
        ICO_COMP_ERROR                        = 2,        //不支持压缩ico图片
        ICO_PARM_ERROR                        = 3,        //无效的参数
        ICO_FILE_ERROR                        = 4,        //文件读取失败,解析结束
}ICO_ERROR;



//软解码BMP图片相关的信息结构
typedef struct
{
        u32 biSizeImage;        //位图数据的大小
        u16 bfOffBits ;         //从文件开始到位图数据(bitmap data)开始之间的的偏移
        u16 biWidth ;                 //图象的宽度,以象素为单位
        u16 biHeight ;                 //图象的高度,以象素为单位
        u8 biBitCount;                //颜色深度
}ICO_INFO;



//解码ICO图片所需的参数(从存储器加载一部分,然后解析一部分)
typedef struct
{
        //描述文件大小与文件缓冲区信息
        u8 *InFileBuff;                                        //输入的原始图片文件缓冲区,至少1KB
        u32 InFileBuffSize;                                        //输入的原始文件缓冲区大小(记录InFileBuff的大小)
        u32 InFileSize;                                        //输入的原始图片的总大小
        //解码像素缓存-用于批量刷新到屏幕,提高效率,大小为4的倍数,如果为0,将不使用加速功能
        u8* DecodeBuff;                                        //解码缓冲区,需要外部初始化
        u32 DecodeBuffSize;                                //解码缓冲区大小

        u16 OutRGB_PixelOffsetX;                        //输出图片像素水平起始偏移-通常是相对于屏幕左上方偏移       
        u16 OutRGB_PixelOffsetY;                        //输出图片像素垂直起始偏移-通常是相对于屏幕左上方偏移       
        u16 OutRGB_ImageWidth;                                //输出图片宽度限制-一般是受屏幕显示范围限制(偏移+图片宽度 <= 屏幕宽度)               
        u16 OutRGB_ImageHeight;                                //输出图片高度限制-一般是受屏幕显示范围限制(偏移+图片高度 <= 屏幕高度)       
        PIC_COLOR_MODE OutRGB_ColorMode;        //输出的RGB数据颜色模式,一定要与画点一致
        //分布加载图片所需的文件句柄
        void *pInFileHandle;                                //输入的原始文件句柄
        bool isLoadFile;                                        //是否需要加载文件,如果已经将所有的图片数据加载到InBmpFileBuff中,设置为FALSE,否则将会通过回调函数进行文件加载
        //文件读取回调(返回:TRUE读取成功;FALSE:读取失败;),需要提前打开文件,并传输文件大小,文件句柄给解析函数,为了与文件系统进行解耦,不用关系文件位置
        //文件读取回调 pFileHandle:文件句柄;Offset:文件偏移(-1:无需偏移,接着上次读取,文件系统一般都有此功能;其它>=0:文件起始偏移;
        //pFileBuff:文件缓冲区;ReadCount:读取大小;pActualReadCount:返回实际读取的文件大小)
        bool (*Func_ReadFile)(void *pFileHandle, int Offset,u8 *pFileBuff, u32 ReadCount, u32 *pActualReadCount);//文件读取回调       
        void* pGramHandle;                                        //画点额外的GRAM句柄,可以根据 Func_DrawPoint()的需要为空,最终传递到 Func_DrawPoint()接口中
        void (*Func_FillPoint)(void* pGramHandle, u16 OffsetX, u16 OffsetY, void* pSourceImage, u16 SourceWidth, u16 SourceHeight);        //像素填充接口
}ICO_Decode_Parm;


ICO_ERROR ICO_Decode(ICO_Decode_Parm* pDecodeParm);//ICO图片软解析(全部解析)

#ifdef __cplusplus
}
#endif
#endif //__ICO_DECODE_H__


使用特权

评论回复
8
八层楼|  楼主 | 2021-8-1 17:07 | 只看该作者
//输出图片颜色模式
typedef enum
{
    PIC_COLOR_ARGB8888  = 0,
    PIC_COLOR_RGB888    = 1,
    PIC_COLOR_RGB565    = 2,
}PIC_COLOR_MODE;








使用特权

评论回复
9
八层楼|  楼主 | 2021-8-1 17:08 | 只看该作者
//简单的封装



/*************************************************************************************************************************
* 函数                        :        bool ICO_Show(const char *pFilePath, GRAM_HANDLE *pGramHandle,u16 OffsetX,u16 OffsetY, u16 ImageWidth, u16 ImageHeight, const char **pErrorStr)
* 功能                        :        显示一张ICO图片到GRAM(分包读取,节省内存)
* 参数                        :        pFilePath:文件路径;pGramHandle:GRAM句柄;OffsetX,OffsetY:显示开始在GRAM中的偏移;ImageWidth,ImageHeight:图片宽高限制;pErrorStr:错误字符串
* 返回                        :        TRUE:成功;FALSE:失败;
* 依赖                        :        底层
* 作者                        :        cp1300@139.com
* 时间                        :        2020-03-12
* 最后修改时间         :         2020-03-12
* 说明                        :         从文件系统显示一张图片到GRAM
                                        ImageWidth,ImageHeight:可以为0,将会自动填充满GRAM剩余位置
*************************************************************************************************************************/
bool ICO_Show(const char* pFilePath, GRAM_HANDLE* pGramHandle, u16 OffsetX, u16 OffsetY, u16 ImageWidth, u16 ImageHeight, const char** pErrorStr)
{
        ICO_Decode_Parm mIcoDecodeParm;        //软解码Ico图片所需的参数
        u8* pFileBuff = NULL;
        FILE_ERROR mFileError;
        u32 FileSize;
        FIL* pFile = NULL;
        bool isStatus = TRUE;
        u8* pDecodeBuff = NULL;

        if (pFilePath == NULL || pGramHandle == NULL)
        {
                if (pErrorStr != NULL && *pErrorStr != NULL) *pErrorStr = "错误:无效的输入参数(路径或句柄).";
                return FALSE;
        }

        //检查X坐标是否合法
        if (OffsetX >= pGramHandle->Width)
        {
                if (pErrorStr != NULL && *pErrorStr != NULL) *pErrorStr = "错误:无效的GRAM宽度或X起始偏移.";
                return FALSE;
        }
        if ((OffsetX + ImageWidth) > pGramHandle->Width || ImageWidth == 0) ImageWidth = pGramHandle->Width - OffsetX;                //限制宽度,不要超出GRAM的限制
        //检查Y坐标是否合法
        if (OffsetY >= pGramHandle->Height)
        {
                if (pErrorStr != NULL && *pErrorStr != NULL) *pErrorStr = "错误:无效的GRAM高度或Y起始偏移.";
                return FALSE;
        }
        if ((OffsetY + ImageHeight) > pGramHandle->Height || ImageHeight == 0) ImageHeight = pGramHandle->Height - OffsetY;        //限制高度,不要超出GRAM的限制
        //从文件系统加载图片
        pFile = FILE_Open(pFilePath, FILE_READ, &mFileError);        //打开文件
        if (pFile == NULL)                                                                                //打开失败
        {
                if (pErrorStr != NULL && *pErrorStr != NULL) *pErrorStr = "错误:打开文件失败.";
                isStatus = FALSE;
                goto close_file;
        }
        FileSize = FILE_GetSize(pFile);
        if (FileSize > _BMP_IMAGE_MAX_SIZE || FileSize <= 50)
        {
                if (pErrorStr != NULL && *pErrorStr != NULL) *pErrorStr = "错误:文件大小不对.";
                isStatus = FALSE;
                goto close_file;
        }
        pFileBuff = mymalloc(SRAMEX, _IMAGE_CACHE_BUFF_SIZE);                                                        //申请内存
        if (pFileBuff == NULL)                                                                                                                        //申请内存失败了
        {
                DEBUG("错误:内存不足.\r\n");
                isStatus = FALSE;
                goto close_file;
        }
        pDecodeBuff = mymalloc(SRAMEX, _IMAGE_DECODE_BUFF_SIZE);                                                        //申请内存
        if (pDecodeBuff == NULL)                                                                                                                        //申请内存失败了
        {
                DEBUG("错误:内存不足.\r\n");
                isStatus = FALSE;
                goto close_file;
        }

        //文件打开成功,开始解析图片
        //描述文件大小与文件缓冲区信息
        mIcoDecodeParm.InFileBuff = pFileBuff;                                                                                        //输入的原始ICO图片文件缓冲区
        mIcoDecodeParm.InFileSize = FileSize;                                                                                        //输入的原始ICO图片文件大小
        mIcoDecodeParm.InFileBuffSize = _IMAGE_CACHE_BUFF_SIZE;                                                        //缓冲区大小
    //解码像素缓存-用于批量刷新到屏幕,提高效率,大小为4的倍数,如果为0,将不使用加速功能
        mIcoDecodeParm.DecodeBuff = pDecodeBuff;                                                                                //解码缓冲区,需要外部初始化
        mIcoDecodeParm.DecodeBuffSize = _IMAGE_DECODE_BUFF_SIZE;                                                //解码缓冲区大小

        mIcoDecodeParm.OutRGB_PixelOffsetX = OffsetX;                                                                        //输出图片像素水平起始偏移-通常是相对于屏幕左上方偏移       
        mIcoDecodeParm.OutRGB_PixelOffsetY = OffsetY;                                                                        //输出图片像素垂直起始偏移-通常是相对于屏幕左上方偏移       
        mIcoDecodeParm.OutRGB_ImageWidth = ImageWidth;                                                                        //输出图片宽度限制-一般是受屏幕显示范围限制(偏移+图片宽度 <= 屏幕宽度)               
        mIcoDecodeParm.OutRGB_ImageHeight = ImageHeight;                                                                //输出图片高度限制-一般是受屏幕显示范围限制(偏移+图片高度 <= 屏幕高度)       
        mIcoDecodeParm.isLoadFile = TRUE;                                                                                                //需加载文件
        mIcoDecodeParm.Func_ReadFile = BMP_ReadFile_CallBack;                                                        //文件读取回调函数
        mIcoDecodeParm.pInFileHandle = pFile;                                                                                        //文件句柄
        mIcoDecodeParm.pGramHandle = pGramHandle;                                                                                //GRAM句柄
        mIcoDecodeParm.Func_FillPoint = ICO_FillPoint_CallBack;
        mIcoDecodeParm.OutRGB_ColorMode = (PIC_COLOR_MODE)pGramHandle->ColorMode;                //输出的RGB数据颜色模式
        if (ICO_Decode(&mIcoDecodeParm) == ICO_OK)                                                                                //ICO图片解析
        {
                if (pErrorStr != NULL && *pErrorStr != NULL) *pErrorStr = "正常";
                isStatus = TRUE;
        }

close_file:
        if (pFileBuff != NULL)
        {
                myfree(SRAMEX, pFileBuff);                                                                                                                //释放掉内存
        }
        if (pDecodeBuff != NULL)
        {
                myfree(SRAMEX, pDecodeBuff);                                                                                                                //释放掉内存
        }
        if (pFile != NULL)
        {
                FILE_Close(pFile, &mFileError);                                                                                                //关闭文件
        }

        return isStatus;
}


使用特权

评论回复
10
八层楼|  楼主 | 2021-8-1 17:09 | 只看该作者
//数据填充接口-填充接口使用的DMA2D功能有硬件的也可以软件实现


//ICO解码所需的填充接口-会执行像素混合
void ICO_FillPoint_CallBack(GRAM_HANDLE* pHandle, u16 OffsetX, u16 OffsetY, void* pSourceImage, u16 SourceWidth, u16 SourceHeight)
{
    static u32 offset;
        static DMA2D_PixelMixing_Parm mMixingParm;                //混合所需参数
       
       
        offset = (u32)pHandle->Width * OffsetY + OffsetX;
        offset *= pHandle->PixelByteSize;
        //=====参数初始化======
        //公共配置
        mMixingParm.ImageWidth                                =        SourceWidth;                                                //待传输的像素图像宽度,单位像素
        mMixingParm.ImageHeight                                =        SourceHeight;                                                //待传输的像素图像高度,单位像素
        //输入的背景配置
        mMixingParm.InBackImageAddr                        =        pHandle->GRAM_Addr + offset;                //输入的背景图像开始地址(通过地址控制开始的X,Y坐标)
        mMixingParm.InBackImageOffsetX                =        pHandle->Width - SourceWidth;                //输入的背景图像跳过的X坐标值
        mMixingParm.InBackAlpha                                =        0xFF;                                                                //输入的背景图像的固定Alpha值,是否使用根据InBackAlphaMode配置决定
        mMixingParm.InBackColorMode                        =        pHandle->ColorMode;                                        //输入的背景图像的颜色模式
        mMixingParm.InBackAlphaMode                        =        DMA2D_ALPHA_NULL;                                        //输入的背景图像Alpha模式
        //输入的前景配置
        mMixingParm.InForeImageAddr                        =        (u32)pSourceImage;                                        //输入的前景图像开始地址(通过地址控制开始的X,Y坐标)
        mMixingParm.InForeImageOffsetX                =        0x00;                                                                //输入的前景图像跳过的X坐标值
        mMixingParm.InForeAlpha                                =        0xFF;                                                                //输入的前景图像的固定Alpha值,是否使用根据InForeAlphaMode配置决定
        mMixingParm.InForeColorMode                        =        DMA2D_COLOR_ARGB8888;                                //输入的前景图像的颜色模式
        mMixingParm.InForeAlphaMode                        =        DMA2D_ALPHA_NULL;                                        //输入的前景图像Alpha模式
        //输出配置
        mMixingParm.OutImageAddr                        =        pHandle->GRAM_Addr + offset;                //输出的图像开始地址(通过地址控制开始的X,Y坐标)
        mMixingParm.OutImageOffsetX                        =        pHandle->Width - SourceWidth;                //输出的图像跳过的X坐标值
        mMixingParm.OutColorMode                        =        pHandle->ColorMode;                                        //输出的图像的颜色模式
       
        DMA2D_WaitTransferComplete(5);                                        //需要等待上一次传输完成-不等待可能会出现乱点
        DMA2D_FillImage_PixelMixing(&mMixingParm);                //DMA2D进行矩形图形填充(会执行像素格式转换与Alpha混合)
}


使用特权

评论回复
11
八层楼|  楼主 | 2021-8-1 17:10 | 只看该作者
//所需接口-文件读取接口


//文件读取回调       
static bool BMP_ReadFile_CallBack(void *pFileHandle, int Offset,u8 *pFileBuff, u32 ReadCount, u32 *pActualReadCount)
{
        FILE_ERROR mFileError;
       
       
        if(Offset >= 0) //需要偏移
        {
                if(FILE_Lseek((FIL*)pFileHandle, Offset, &mFileError) == FALSE)
                {
                        DEBUG("lseek错误:%d\r\n", mFileError);
                        return FALSE;
                }
        }
        if(FILE_Read((FIL*)pFileHandle, pFileBuff, ReadCount, pActualReadCount, &mFileError) == FALSE)
        {
                DEBUG("Read错误:%d\r\n", mFileError);
                return FALSE;
        }
       
        return TRUE;
}


使用特权

评论回复
12
八层楼|  楼主 | 2021-8-1 17:11 | 只看该作者
如果硬件不支持Alphe混合,需要自己在画点函数中实现混合

//测试代码-请自己提前准备好测试图片

ICO_Show("C:\\256x256.ico", pLTDC_Layer2_GRAM_HANDLE, 100, 30, 240, 240, &pErrorStr);

测试效果如下(中间的图标就是ico图标)

电脑上面的这个图标


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

91

主题

4146

帖子

2

粉丝