上篇讲述了一维FFT的GPU实现(FFT算法实现——基于GPU的基2快速傅里叶变换),后来我又由于需要做了一下二维FFT,大概思路如下。

首先看的肯定是公式:

如上面公式所描述的,2维FFT只需要拆分成行FFT,和列FFT就行了,其中我在下面的实现是假设原点在F(0,0),由于我的代码需要原点在中心,所以在最后我将原点移动到了中心。

下面是原点F(0,0)的2维FFT的伪代码:

  1. //C2DFFT
  2. //被执行2DFFT的是一个N*N的矩阵,在source_2d中按行顺序储存
  3. //水平方向FFT
  4. for (int i=;i<N;i++)
  5. {
  6. fft1(&source_2d[i*N],&source_2d_1[i*N],N);
  7. }
  8. //转置列成行
  9. for (int i=;i<N*N;i++)
  10. {
  11. int x = i%N;
  12. int y = i/N;
  13. int index = x*N+y;
  14. source_2d[index] = source_2d_1[i];
  15. }
  16. //垂直FFT
  17. for(int i=;i<N;i++)
  18. {
  19. fft1(&source_2d[i*N],&source_2d_1[i*N],N);
  20. }
  21. //转置回来
  22. for (int i=;i<N*N;i++)
  23. {
  24. int x = i%N;
  25. int y = i/N;
  26. int index = x*N+y;
  27. source_2d[index] = source_2d_1[i];
  28. }

GPU实现无非把这些东西转换到GPU上。

我基于OpenGL的fragment shader来计算fft;数据都存放在纹理或者FBO里面。和1维fft不同的是,NXN的数据里面,只是对当前列或者当前排做一维FFT,所以bit反转表只需要一个1*N的buffer就可以了。对应的蝴蝶图数据也只需要1*N即可。所以我们有如下的分配:

  1. static ofFbo _fbo_bitrev_table;
  2. static ofFbo _origin_butterfly_2d;
  3.  
  4. _fbo_bitrev_table.allocate(N,,GL_RGBA32F);
  5. _origin_butterfly_2d.allocate(N,,GL_RGBA32F);

首先要做的是把长度为N的bit反转表求出来,这个只需要求一次,所以在最开始的时候就用CPU求出来:

  1. for(int i=;i<N;i++)
  2. {
  3. _bitrev_index_2d.setColor(i,,ofFloatColor(bit_rev(i,N-),,,));
  4. }
  5.  
  6. _bitrev_index_2d.update();
  7.  
  8. //翻转后的索引
  9. _fbo_bitrev_table.begin();
  10. _bitrev_index_2d.draw(,,N,);
  11. _fbo_bitrev_table.end();

然后初始化最初的蝴蝶图,这个和1维FFT是一样的,只是长度不同而已:

  1. for(int i=;i<N;i++)
  2. {
  3. //初始化二维蝴蝶图
  4. if(i%==)
  5. {
  6. _data_2d.setColor(i,,ofFloatColor(.f,.f,,i+));
  7. }
  8. else
  9. {
  10. _data_2d.setColor(i,,ofFloatColor(.f,.f,,i-));
  11. }
  12.  
  13. }
  14.  
  15. _data_2d.update();
  16.  
  17. /////////////////2D初始化/////////////////
  18. //初始化2D蝴蝶图
  19. _weight_index_2d[].begin();
  20. _data_2d.draw(,,N,);
  21. _weight_index_2d[].end();
  22. //备份2D初始蝴蝶图,用于下一次新的计算
  23. _origin_butterfly_2d.begin();
  24. _data_2d.draw(,,N,);
  25. _origin_butterfly_2d.end();

辅助函数:

  1. static unsigned int bit_rev(unsigned int v, unsigned int maxv)
  2. {
  3. unsigned int t = log(maxv + )/log();
  4. unsigned int ret = ;
  5. unsigned int s = 0x80000000>>();
  6. for (unsigned int i = ; i < t; ++i)
  7. {
  8. unsigned int r = v&(s << i);
  9. ret |= (r << (t-i-)) >> (i);
  10. }
  11. return ret;
  12. }
  13.  
  14. static void bit_reverse_copy(RBVector2 src[], RBVector2 des[], int len)
  15. {
  16. for (int i = ; i < len;i++)
  17. {
  18. des[bit_rev(i, len-)] = src[i];
  19. }
  20. }

下面定义计算2维IFFT的函数:

  1. void GPUFFT::ifft_2d(ofFbo& in,ofFbo& out,int size);

其中in是输入,out是输出,size就是N,由初始化的时候传入了一次,在这里写是为了方便调试的时候临时改变尺寸。

Ifft本身的代码和上面形式一样,内容变成了各种shader计算:

  1. void GPUFFT::ifft_2d(ofFbo& in,ofFbo& out,int size)
  2. {
  3. //禁用Alpha混合,否则绘制到FBO会混合Alpha,造成数据丢失
  4. ofDisableAlphaBlending();
  5.  
  6. //水平FFT
  7. _weight_index_2d[_cur_2d].begin();
  8. _origin_butterfly_2d.draw(,,N,);
  9. _weight_index_2d[_cur_2d].end();
  10.  
  11. _fbo_in_bitreved_2d.begin();
  12. _bit_reverse_shader_2d.begin();
  13. _bit_reverse_shader_2d.setUniform3f("iResolution",N,N,);
  14. _bit_reverse_shader_2d.setUniform1i("N",N);
  15. _bit_reverse_shader_2d.setUniform1i("dir",);
  16. _bit_reverse_shader_2d.setUniformTexture("tex_origin",in.getTextureReference(),);
  17. _bit_reverse_shader_2d.setUniformTexture("tex_bitreverse_table",_fbo_bitrev_table.getTextureReference(),);
  18. ofRect(,,N,N);
  19. _bit_reverse_shader_2d.end();
  20. _fbo_in_bitreved_2d.end();
  21.  
  22. //翻转后的数据
  23. _res_back_2d[_cur_2d].begin();
  24. _fbo_in_bitreved_2d.draw(,,N,N);
  25. _res_back_2d[_cur_2d].end();
  26.  
  27. for(int i = ;i<N;i*=)
  28. {
  29. _res_back_2d[-_cur_2d].begin();
  30. ofClear(,,,);
  31. _gpu_fft_shader_2d.begin();
  32. _gpu_fft_shader_2d.setUniform1i("size",N);
  33. _gpu_fft_shader_2d.setUniform1i("n_step",i);
  34. _gpu_fft_shader_2d.setUniform3f("iResolution",N,N,);
  35. _gpu_fft_shader_2d.setUniform1i("dir",);
  36. _gpu_fft_shader_2d.setUniformTexture("tex_index_weight",_weight_index_2d[_cur_2d].getTextureReference(),);
  37. _gpu_fft_shader_2d.setUniformTexture("tex_res_back",_res_back_2d[_cur_2d].getTextureReference(),);
  38. //_gpu_fft_shader_2d.setUniformTexture("test",imag_test.getTextureReference(),4);
  39.  
  40. ofRect(,,N,N);
  41.  
  42. _gpu_fft_shader_2d.end();
  43.  
  44. _res_back_2d[-_cur_2d].end();
  45.  
  46. _weight_index_2d[-_cur_2d].begin();
  47. ofClear(,,,);
  48.  
  49. _weight_index_shader_2d.begin();
  50. _weight_index_shader_2d.setUniform1i("size",N);
  51. _weight_index_shader_2d.setUniform1i("n_step",i);
  52. _weight_index_shader_2d.setUniform3f("iResolution",N,,);
  53. _weight_index_shader_2d.setUniform1i("dir",);
  54. _weight_index_shader_2d.setUniformTexture("tex_input",_weight_index_2d[_cur_2d].getTextureReference(),);
  55.  
  56. ofRect(,,N,);
  57.  
  58. _weight_index_shader_2d.end();
  59.  
  60. _weight_index_2d[-_cur_2d].end();
  61.  
  62. _cur_2d = - _cur_2d;
  63. }
  64.  
  65. //for ifft
  66. _res_back_2d[-_cur_2d].begin();
  67. _res_back_2d[_cur_2d].draw(,,N,N);
  68. _res_back_2d[-_cur_2d].end();
  69.  
  70. _res_back_2d[_cur_2d].begin();
  71. _ifft_div_shader_2d.begin();
  72. _ifft_div_shader_2d.setUniform1i("N",N);
  73. _ifft_div_shader_2d.setUniform3f("iResolution",N,N,);
  74. _ifft_div_shader_2d.setUniformTexture("tex_rgb",_res_back_2d[-_cur_2d].getTextureReference(),);
  75. ofRect(,,N,N);
  76. _ifft_div_shader_2d.end();
  77. _res_back_2d[_cur_2d].end();
  78.  
  79. //垂直FFT
  80. //垂直方向的所有都是计算都按照垂直方向来
  81. _weight_index_2d[_cur_2d].begin();
  82. _origin_butterfly_2d.draw(,,N,);
  83. _weight_index_2d[_cur_2d].end();
  84.  
  85. //这一步不会将垂直水平化
  86. _fbo_in_bitreved_2d.begin();
  87. _bit_reverse_shader_2d.begin();
  88. _bit_reverse_shader_2d.setUniform3f("iResolution",N,N,);
  89. _bit_reverse_shader_2d.setUniform1i("N",N);
  90. _bit_reverse_shader_2d.setUniform1i("dir",);
  91. _bit_reverse_shader_2d.setUniformTexture("tex_origin",_res_back_2d[_cur_2d].getTextureReference(),);
  92. _bit_reverse_shader_2d.setUniformTexture("tex_bitreverse_table",_fbo_bitrev_table.getTextureReference(),);
  93. ofRect(,,N,N);
  94. _bit_reverse_shader_2d.end();
  95. _fbo_in_bitreved_2d.end();
  96.  
  97. //翻转后的数据
  98. _res_back_2d[_cur_2d].begin();
  99. _fbo_in_bitreved_2d.draw(,,N,N);
  100. _res_back_2d[_cur_2d].end();
  101.  
  102. for(int i = ;i<N;i*=)
  103. {
  104. _res_back_2d[-_cur_2d].begin();
  105. ofClear(,,,);
  106. _gpu_fft_shader_2d.begin();
  107. _gpu_fft_shader_2d.setUniform1i("size",N);
  108. _gpu_fft_shader_2d.setUniform1i("n_step",i);
  109. _gpu_fft_shader_2d.setUniform3f("iResolution",N,N,);
  110. _gpu_fft_shader_2d.setUniform1i("dir",);
  111. _gpu_fft_shader_2d.setUniformTexture("tex_index_weight",_weight_index_2d[_cur_2d].getTextureReference(),);
  112. _gpu_fft_shader_2d.setUniformTexture("tex_res_back",_res_back_2d[_cur_2d].getTextureReference(),);
  113. //_gpu_fft_shader_2d.setUniformTexture("test",imag_test.getTextureReference(),4);
  114.  
  115. ofRect(,,N,N);
  116.  
  117. _gpu_fft_shader_2d.end();
  118.  
  119. _res_back_2d[-_cur_2d].end();
  120.  
  121. _weight_index_2d[-_cur_2d].begin();
  122. ofClear(,,,);
  123.  
  124. _weight_index_shader_2d.begin();
  125. _weight_index_shader_2d.setUniform1i("size",N);
  126. _weight_index_shader_2d.setUniform1i("n_step",i);
  127. _weight_index_shader_2d.setUniform3f("iResolution",N,,);
  128. _weight_index_shader_2d.setUniform1i("dir",);
  129. _weight_index_shader_2d.setUniformTexture("tex_input",_weight_index_2d[_cur_2d].getTextureReference(),);
  130.  
  131. ofRect(,,N,);
  132.  
  133. _weight_index_shader_2d.end();
  134.  
  135. _weight_index_2d[-_cur_2d].end();
  136.  
  137. _cur_2d = - _cur_2d;
  138. }
  139.  
  140. //for ifft
  141. _res_back_2d[-_cur_2d].begin();
  142. _res_back_2d[_cur_2d].draw(,,N,N);
  143. _res_back_2d[-_cur_2d].end();
  144.  
  145. _res_back_2d[_cur_2d].begin();
  146. _ifft_div_shader_2d.begin();
  147. _ifft_div_shader_2d.setUniform1i("N",N);
  148. _ifft_div_shader_2d.setUniform3f("iResolution",N,N,);
  149. _ifft_div_shader_2d.setUniformTexture("tex_rgb",_res_back_2d[-_cur_2d].getTextureReference(),);
  150. ofRect(,,N,N);
  151. _ifft_div_shader_2d.end();
  152. _res_back_2d[_cur_2d].end();
  153.  
  154. out.begin();
  155. _res_back_2d[_cur_2d].draw(,,N,N);
  156. out.end();
  157.  
  158. //恢复Alpha混合
  159. //ofEnableAlphaBlending();
  160. }

现在来看shader内容:

_bit_reverse_shader_2d

这个shader用于将整个N*N的数据全部按照行或者按照列进行翻装,使之满足执行fft的条件:

  1. #version
  2. uniform sampler2D tex_origin;
  3. //1xN查找表,用于查找索引对应的bitreverse数
  4. uniform sampler2D tex_bitreverse_table;
  5. //1 for x direction,2 for y direction
  6. uniform int dir;
  7. uniform int N;
  8. uniform vec3 iResolution;
  9.  
  10. out vec4 outColor;
  11.  
  12. void main()
  13. {
  14. vec2 tex_coord = gl_FragCoord.xy/iResolution.xy;
  15.  
  16. vec2 table_index;
  17. table_index.y = 0.5;
  18. if(dir==)
  19. table_index.x = tex_coord.x;
  20. else
  21. table_index.x = tex_coord.y;
  22. float bitreverse = texture(tex_bitreverse_table,table_index).r;
  23.  
  24. vec2 origin_index;
  25. if(dir==)
  26. {
  27. //x方向
  28. origin_index.y = tex_coord.y;
  29. origin_index.x = (bitreverse+0.5)/N;
  30. }
  31. else
  32. {
  33. //y方向
  34. origin_index.x = tex_coord.x;
  35. origin_index.y = (bitreverse+0.5)/N;
  36. }
  37. vec2 param = texture(tex_origin,origin_index).xy;
  38.  
  39. outColor = vec4(param,,);
  40. }

_gpu_fft_shader_2d

这是fft执行计算的部分,同样分为按行和按列:

  1. #version
  2. //NX1
  3. uniform sampler2D tex_index_weight;
  4. //NXN
  5. uniform sampler2D tex_res_back;
  6. uniform sampler2D test;
  7. uniform int size;
  8. uniform int n_step;
  9. //1 for x direction,2 for y direction
  10. uniform int dir;
  11.  
  12. uniform vec3 iResolution;
  13.  
  14. out vec4 outColor;
  15.  
  16. void main()
  17. {
  18. vec2 tex_coord = gl_FragCoord.xy/iResolution.xy;
  19.  
  20. vec2 first_index;
  21. if(dir==)
  22. {
  23. first_index.y = 0.5;
  24. first_index.x = tex_coord.x;
  25. }
  26. else
  27. {
  28. first_index.y = 0.5;
  29. first_index.x = tex_coord.y;
  30. }
  31.  
  32. float cur_x = gl_FragCoord.x - 0.5;
  33. float cur_y = gl_FragCoord.y - 0.5;
  34.  
  35. vec2 outv;
  36.  
  37. vec4 temp = texture(tex_index_weight,first_index);
  38. //ifft
  39. vec2 weight = vec2(cos(temp.r/temp.g**3.141592653),-sin(temp.r/temp.g**3.141592653));
  40. //fft
  41. //vec2 weight = vec2(cos(temp.r/temp.g*2*3.141592653),sin(temp.r/temp.g*2*3.141592653));
  42. vec2 _param2_index;
  43.  
  44. if(dir==)
  45. {
  46. _param2_index.x = (temp.a + 0.5)/size;
  47. _param2_index.y = tex_coord.y;
  48. }
  49. else
  50. {
  51. _param2_index.x = tex_coord.x;
  52. _param2_index.y = (temp.a + 0.5)/size;
  53. }
  54.  
  55. vec2 param1 = texture(tex_res_back,tex_coord).rg;
  56. vec2 param2 = texture(tex_res_back,_param2_index).rg;
  57.  
  58. float tex_coord_n1;
  59. float tex_coord_n2;
  60. if(dir==)
  61. {
  62. tex_coord_n1 = cur_x;
  63. }
  64. else
  65. {
  66. tex_coord_n1 = cur_y;
  67. }
  68.  
  69. tex_coord_n2 = temp.a;
  70.  
  71. if(tex_coord_n1<tex_coord_n2)
  72. {
  73. outv.r = param1.r + param2.r*weight.r-weight.g*param2.g;
  74. outv.g = param1.g +weight.r*param2.g + weight.g*param2.r;
  75. }
  76. else
  77. {
  78. outv.r = param2.r + param1.r*weight.r-weight.g*param1.g;
  79. outv.g = param2.g +weight.r*param1.g + weight.g*param1.r;
  80. }
  81.  
  82. outColor = vec4(outv,,);
  83.  
  84. }

_weight_index_shader_2d

更新蝴蝶图索引:

  1. #version
  2.  
  3. uniform sampler2D tex_input;
  4. uniform int size;
  5. uniform int n_total;
  6. //start with 2
  7. uniform int n_step;
  8. //1 for x direction,2 for y direction
  9. uniform int dir;
  10.  
  11. uniform vec3 iResolution;
  12. out vec4 outColor;
  13.  
  14. void main()
  15. {
  16. vec2 tex_coord = gl_FragCoord.xy/iResolution.xy;
  17. vec4 fetch = texture(tex_input,tex_coord);
  18. float cur_x = gl_FragCoord.x - 0.5;
  19. float cur_y = gl_FragCoord.y - 0.5;
  20.  
  21. vec4 outv;
  22. float tex_coord_n;
  23. if(dir==)
  24. {
  25. //x dir
  26. tex_coord_n = cur_x;
  27. }
  28. else
  29. {
  30. //y dir
  31. tex_coord_n = cur_x;
  32. }
  33.  
  34. //updata weight
  35. vec2 pre_w = fetch.rg;
  36. float i = pre_w.r;
  37. float n = pre_w.g;
  38. float new_i;
  39. float new_n;
  40. new_i = i;
  41. new_n = n*;
  42. if(int(tex_coord_n)%(n_step*) > n_step*-)
  43. {
  44. new_i += n_step*;
  45. }
  46. outv.r = new_i;
  47. outv.g = new_n;
  48. //outv.rg = tex_coord;
  49.  
  50. //updata index
  51. vec2 pre_index = fetch.ba;
  52. int x = int(pre_index.x);
  53. int y = int(pre_index.y);
  54. int ni = n_step*;
  55. float new_tex_coord_n = tex_coord_n;
  56. if((int(tex_coord_n)/ni)%==)
  57. {
  58. new_tex_coord_n += ni;
  59. }
  60. else
  61. {
  62. new_tex_coord_n -= ni;
  63. }
  64.  
  65. outv.b = ;
  66. outv.a = new_tex_coord_n;
  67. outColor = outv;
  68. //outColor = vec4(tex_coord_n,tex_coord_n%n_step,tex_coord_n%n_step,tex_coord_n%n_step);
  69. }

最后的

_ifft_div_shader_2d

是为了计算ifft,将每个计算结果除以一个N:

  1. #version
  2. uniform sampler2D tex_rgb;
  3. uniform int N;
  4. uniform vec3 iResolution;
  5.  
  6. out vec4 outColor;
  7.  
  8. void main()
  9. {
  10. vec2 tex_coord = gl_FragCoord.xy/iResolution.xy;
  11.  
  12. vec2 outv;
  13.  
  14. vec4 c = texture(tex_rgb,tex_coord);
  15.  
  16. outv.r = c.r/N;
  17. outv.g = c.g/N;
  18. outColor = vec4(outv,,);
  19. }

最后,out里面就是结果了。

对于将原点移动到中心多了以下shader:

  1. vec4 c;
  2. if(tex_coord.x>0.5&&tex_coord.y>0.5)
  3. {
  4. c = texture(tex_rgb,tex_coord-vec2(0.5,0.5));
  5.  
  6. }
  7. if(tex_coord.x>0.5&&tex_coord.y<0.5)
  8. {
  9. c = texture(tex_rgb,tex_coord+vec2(-0.5,0.5));
  10. }
  11. if(tex_coord.x<0.5&&tex_coord.y>0.5)
  12. {
  13. c = texture(tex_rgb,tex_coord+vec2(0.5,-0.5));
  14. }
  15. if(tex_coord.x<0.5&&tex_coord.y<0.5)
  16. {
  17. c = texture(tex_rgb,tex_coord+vec2(0.5,0.5));
  18. }
  19. outColor = c;

2维FFT算法实现——基于GPU的基2快速二维傅里叶变换的更多相关文章

  1. FFT算法实现——基于GPU的基2快速傅里叶变换

    最近做一个东西,要用到快速傅里叶变换,抱着蛋疼的心态,自己尝试写了一下,遇到一些问题. 首先看一下什么叫做快速傅里叶变换(FFT)(来自Wiki): 快速傅里叶变换(英语:Fast Fourier T ...

  2. JAVA描述算法和数据结构(01):稀疏数组和二维数组转换

    本文源码:GitHub·点这里 || GitEE·点这里 一.基本简介 1.基础概念 在矩阵中,若数值为0的元素数目远远多于非0元素的数目,并且非0元素分布没有规律时,则称该矩阵为稀疏矩阵:与之相反, ...

  3. 功能要求:定义一个两行三列的二维数组 names 并赋值,使用二重循环输出二维数组中的元素。

    功能要求:定义一个两行三列的二维数组 names 并赋值,使用二重循环输出二维数组中的元素 names={{"tom","jack","mike&qu ...

  4. 【算法系列学习】codeforces D. Mike and distribution 二维贪心

    http://codeforces.com/contest/798/problem/D http://blog.csdn.net/yasola/article/details/70477816 对于二 ...

  5. Android二维码开源项目zxing用例简化和生成二维码、条形码

    上一篇讲到:Android二维码开源项目zxing编译,编译出来后有一个自带的測试程序:CaptureActivity比較复杂,我仅仅要是把一些不用的东西去掉,用看起来更方便,二维码和条形码的流行性自 ...

  6. 今天网站后台登录页面需要生成一个二维码,然后在手机app上扫描这个二维码,实现网站登录的效果及其解决方案如下

    要实现二维码登录,需要解决2个技术,1.需要js websocket 与后台php实现长连接技术 2.实现二维码生成技术 要实现这个功能第二个算是比较简单,只需要下载一个php的二维码生成器即可,但要 ...

  7. 微信生成二维码 只需一个网址即刻 还有jquery生成二维码

    <div class="orderDetails-info"> <img src="http://qr.topscan.com/api.php?text ...

  8. 二维码解析:使用 JavaScript 库reqrcode.js解析二维码

    上次使用QRCode.js可以来生成二维码,但是我没有找到有文档说明可以对存在的二维码进行扫描解析其中的内容. 幸亏查找到了可行的解决方案,而且很好使哦!就是reqrcode.js 地址:https: ...

  9. 二维码生成:使用 JavaScript 库QRCode.js生成二维码

    QRCode.js:跨浏览器的javascript二维码生成库,支持html5的Canvas画布,没有任何依赖. Github 地址:https://github.com/davidshimjs/qr ...

随机推荐

  1. jreble安装 in idea

    http://www.cnblogs.com/littlehb/archive/2013/04/19/3031045.html

  2. windows下安装node环境,以及grunt试水笔记

    grunt,当下前端界知名度最高的工作流处理工具. 在一线的互联网公司,它早已经被用烂了,而我真正接触,是在去年年底... 期间还因为内心太杂分心玩乐而荒废学途,以致到最近才重拾学业,在这里BS一下自 ...

  3. spring mvc中的控制器方法中的参数从哪里传进来?

    编写控制器方法的时候很奇怪,spring是怎么知道你控制器方法的参数类型,并且注入正确的对象呢? 比如下面这样 @RequestMapping(value="/register", ...

  4. 转载:怎样用通俗的语言解释REST,以及RESTful?

    作者:覃超链接:https://www.zhihu.com/question/28557115/answer/48094438来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出 ...

  5. OpenGL初识

    OpenGL 概念 OpenGL提供的是一系列接口, 它是指一个规范, OpenGL规范严格规定了每个函数该如何执行, 以及它们的输出值, 具体的实现是由各个显示设备厂商, 它作为本地系统库直接运行在 ...

  6. php对图片加水印--将一张图片作为水印加到另一张图片

    代码如下: /**  * 图片加水印(适用于png/jpg/gif格式)  *  * @param $srcImg  原图片  * @param $waterImg 水印图片  * @param $s ...

  7. 使用union合并查询

    语法: select …..from 表1 union select ……from 表2 2. 合并查询的特点 ① 合并的表中的列的个数.数据类型必须相同或向兼容. ② union默认去掉重复值,如果 ...

  8. CSS代码优化(转载)

    要点1:css代码优化作用与意义 1.减少占用网页字节.在同等条件下缩短浏览器下载css代码时间,相当于加快网页打开速度:2.便于维护.简化和标准化css代码让css代码减少,便于日后维护:3.让自己 ...

  9. 用 Redis Desktop Manager 远程连接 redis 数据库。

    环境: 本机OS:window 10(本机没有安装redis) redis 服务器:centos 7 使用 Redis Desktop Manager 工具远程连接 redis. Redis Desk ...

  10. [android] 安卓自定义样式和主题

    简单练习自定义样式和主题,样式是加在View上,主题是加在Application或者Activity上 styles.xml <?xml version="1.0" enco ...