comparison layer/WaveformLayer.cpp @ 1367:f5566f7271fe waverevision

Rework waveform renderer to use smooth paths, aiming to get near-pixel-identical results when zoomed out far enough for a single path not to be relevant
author Chris Cannam
date Wed, 31 Oct 2018 15:06:32 +0000
parents c2a3ac0a6688
children ca9a36a5ab76
comparison
equal deleted inserted replaced
1366:c2a3ac0a6688 1367:f5566f7271fe
43 SingleColourLayer(), 43 SingleColourLayer(),
44 m_model(0), 44 m_model(0),
45 m_gain(1.0f), 45 m_gain(1.0f),
46 m_autoNormalize(false), 46 m_autoNormalize(false),
47 m_showMeans(true), 47 m_showMeans(true),
48 m_greyscale(true),
49 m_channelMode(SeparateChannels), 48 m_channelMode(SeparateChannels),
50 m_channel(-1), 49 m_channel(-1),
51 m_scale(LinearScale), 50 m_scale(LinearScale),
52 m_middleLineHeight(0.5), 51 m_middleLineHeight(0.5),
53 m_aggressive(false), 52 m_aggressive(false),
272 m_cacheValid = false; 271 m_cacheValid = false;
273 emit layerParametersChanged(); 272 emit layerParametersChanged();
274 } 273 }
275 274
276 void 275 void
277 WaveformLayer::setUseGreyscale(bool useGreyscale)
278 {
279 if (m_greyscale == useGreyscale) return;
280 m_greyscale = useGreyscale;
281 m_cacheValid = false;
282 emit layerParametersChanged();
283 }
284
285 void
286 WaveformLayer::setChannelMode(ChannelMode channelMode) 276 WaveformLayer::setChannelMode(ChannelMode channelMode)
287 { 277 {
288 if (m_channelMode == channelMode) return; 278 if (m_channelMode == channelMode) return;
289 m_channelMode = channelMode; 279 m_channelMode = channelMode;
290 m_cacheValid = false; 280 m_cacheValid = false;
354 unit = "dB"; 344 unit = "dB";
355 } 345 }
356 return true; 346 return true;
357 } 347 }
358 348
359 int 349 double
360 WaveformLayer::dBscale(double sample, int m) const 350 WaveformLayer::dBscale(double sample, int m) const
361 { 351 {
362 if (sample < 0.0) return dBscale(-sample, m); 352 if (sample < 0.0) return dBscale(-sample, m);
363 double dB = AudioLevel::multiplier_to_dB(sample); 353 double dB = AudioLevel::multiplier_to_dB(sample);
364 if (dB < -50.0) return 0; 354 if (dB < -50.0) return 0;
365 if (dB > 0.0) return m; 355 if (dB > 0.0) return m;
366 return int(((dB + 50.0) * m) / 50.0 + 0.1); 356 return ((dB + 50.0) * m) / 50.0;
367 } 357 }
368 358
369 int 359 int
370 WaveformLayer::getChannelArrangement(int &min, int &max, 360 WaveformLayer::getChannelArrangement(int &min, int &max,
371 bool &merging, bool &mixing) 361 bool &merging, bool &mixing)
545 535
546 } else { 536 } else {
547 paint = &viewPainter; 537 paint = &viewPainter;
548 } 538 }
549 539
550 paint->setRenderHint(QPainter::Antialiasing, false); 540 paint->setRenderHint(QPainter::Antialiasing, true);
551 541
552 if (m_middleLineHeight != 0.5) { 542 if (m_middleLineHeight != 0.5) {
553 paint->save(); 543 paint->save();
554 double space = m_middleLineHeight * 2; 544 double space = m_middleLineHeight * 2;
555 if (space > 1.0) space = 2.0 - space; 545 if (space > 1.0) space = 2.0 - space;
762 channels = getChannelArrangement(minChannel, maxChannel, 752 channels = getChannelArrangement(minChannel, maxChannel,
763 mergingChannels, mixingChannels); 753 mergingChannels, mixingChannels);
764 if (channels == 0) return; 754 if (channels == 0) return;
765 755
766 QColor baseColour = getBaseQColor(); 756 QColor baseColour = getBaseQColor();
767 vector<QColor> greys = getPartialShades(v);
768
769 QColor midColour = baseColour; 757 QColor midColour = baseColour;
758
770 if (midColour == Qt::black) { 759 if (midColour == Qt::black) {
771 midColour = Qt::gray; 760 midColour = Qt::gray;
772 } else if (v->hasLightBackground()) { 761 } else if (v->hasLightBackground()) {
773 midColour = midColour.light(150); 762 midColour = midColour.light(150);
774 } else { 763 } else {
775 midColour = midColour.light(50); 764 midColour = midColour.light(50);
776 } 765 }
777 766
778 int prevRangeBottom = -1, prevRangeTop = -1;
779 QColor prevRangeBottomColour = baseColour, prevRangeTopColour = baseColour;
780
781 double gain = m_effectiveGains[ch]; 767 double gain = m_effectiveGains[ch];
782 768
783 int m = (h / channels) / 2; 769 int m = (h / channels) / 2;
784 int my = m + (((ch - minChannel) * h) / channels); 770 int my = m + (((ch - minChannel) * h) / channels);
785 771
793 m_channelMode != MergeChannels) { 779 m_channelMode != MergeChannels) {
794 m = (h / channels); 780 m = (h / channels);
795 my = m + (((ch - minChannel) * h) / channels); 781 my = m + (((ch - minChannel) * h) / channels);
796 } 782 }
797 783
798 paint->setPen(greys[1]); 784 // Horizontal axis along middle
785 paint->setPen(QPen(midColour, 0));
799 paint->drawLine(x0, my, x1, my); 786 paint->drawLine(x0, my, x1, my);
800 787
801 paintChannelScaleGuides(v, paint, rect, ch); 788 paintChannelScaleGuides(v, paint, rect, ch);
802 789
803 int rangeix = ch - minChannel; 790 int rangeix = ch - minChannel;
806 SVCERR << "paint channel " << ch << ": frame0 = " << frame0 << ", frame1 = " << frame1 << ", blockSize = " << blockSize << ", have " << ranges.size() << " range blocks of which ours is index " << rangeix << " with " << ranges[rangeix].size() << " ranges in it" << endl; 793 SVCERR << "paint channel " << ch << ": frame0 = " << frame0 << ", frame1 = " << frame1 << ", blockSize = " << blockSize << ", have " << ranges.size() << " range blocks of which ours is index " << rangeix << " with " << ranges[rangeix].size() << " ranges in it" << endl;
807 #else 794 #else
808 (void)frame1; // not actually used 795 (void)frame1; // not actually used
809 #endif 796 #endif
810 797
798 QPainterPath waveformPath;
799 QPainterPath meanPath;
800 QPainterPath clipPath;
801 vector<QPointF> individualSamplePoints;
802
803 bool firstPoint = true;
804
811 for (int x = x0; x <= x1; ++x) { 805 for (int x = x0; x <= x1; ++x) {
812 806
813 sv_frame_t f0, f1; 807 sv_frame_t f0, f1;
814 sv_frame_t i0, i1; 808 sv_frame_t i0, i1;
815 809
861 SVCERR << "No (or not enough) ranges for index i0 = " << i0 << " (there are " << r.size() << " range(s))" << endl; 855 SVCERR << "No (or not enough) ranges for index i0 = " << i0 << " (there are " << r.size() << " range(s))" << endl;
862 #endif 856 #endif
863 continue; 857 continue;
864 } 858 }
865 859
866 int rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0; 860 double rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0;
867 861
868 if (mergingChannels && ranges.size() > 1) { 862 if (mergingChannels && ranges.size() > 1) {
869 863
870 const auto &other = ranges[1]; 864 const auto &other = ranges[1];
871 865
893 range.setMin((range.min() + other[i0].min()) / 2); 887 range.setMin((range.min() + other[i0].min()) / 2);
894 range.setAbsmean((range.absmean() + other[i0].absmean()) / 2); 888 range.setAbsmean((range.absmean() + other[i0].absmean()) / 2);
895 } 889 }
896 } 890 }
897 891
898 int greyLevels = 1;
899 if (m_greyscale && (m_scale == LinearScale)) greyLevels = 4;
900
901 switch (m_scale) { 892 switch (m_scale) {
902 893
903 case LinearScale: 894 case LinearScale:
904 rangeBottom = int(double(m * greyLevels) * range.min() * gain); 895 rangeBottom = range.min() * gain * m;
905 rangeTop = int(double(m * greyLevels) * range.max() * gain); 896 rangeTop = range.max() * gain * m;
906 meanBottom = int(double(-m) * range.absmean() * gain); 897 meanBottom = range.absmean() * gain * (-m);
907 meanTop = int(double(m) * range.absmean() * gain); 898 meanTop = range.absmean() * gain * m;
908 break; 899 break;
909 900
910 case dBScale: 901 case dBScale:
911 if (!mergingChannels) { 902 if (!mergingChannels) {
912 int db0 = dBscale(range.min() * gain, m); 903 double db0 = dBscale(range.min() * gain, m);
913 int db1 = dBscale(range.max() * gain, m); 904 double db1 = dBscale(range.max() * gain, m);
914 rangeTop = std::max(db0, db1); 905 rangeTop = std::max(db0, db1);
915 meanTop = std::min(db0, db1); 906 meanTop = std::min(db0, db1);
916 if (mixingChannels) rangeBottom = meanTop; 907 if (mixingChannels) rangeBottom = meanTop;
917 else rangeBottom = dBscale(range.absmean() * gain, m); 908 else rangeBottom = dBscale(range.absmean() * gain, m);
909 meanBottom = rangeBottom;
910 } else {
911 rangeBottom = -dBscale(range.min() * gain, m);
912 rangeTop = dBscale(range.max() * gain, m);
913 meanBottom = -dBscale(range.absmean() * gain, m);
914 meanTop = dBscale(range.absmean() * gain, m);
915 }
916 break;
917
918 case MeterScale:
919 if (!mergingChannels) {
920 double r0 = fabs(AudioLevel::multiplier_to_preview
921 (range.min() * gain, m));
922 double r1 = fabs(AudioLevel::multiplier_to_preview
923 (range.max() * gain, m));
924 rangeTop = std::max(r0, r1);
925 meanTop = std::min(r0, r1);
926 if (mixingChannels) rangeBottom = meanTop;
927 else rangeBottom = AudioLevel::multiplier_to_preview
928 (range.absmean() * gain, m);
918 meanBottom = rangeBottom; 929 meanBottom = rangeBottom;
919 } else { 930 } else {
920 rangeBottom = -dBscale(range.min() * gain, m * greyLevels); 931 rangeBottom = -AudioLevel::multiplier_to_preview
921 rangeTop = dBscale(range.max() * gain, m * greyLevels); 932 (range.min() * gain, m);
922 meanBottom = -dBscale(range.absmean() * gain, m); 933 rangeTop = AudioLevel::multiplier_to_preview
923 meanTop = dBscale(range.absmean() * gain, m); 934 (range.max() * gain, m);
935 meanBottom = -AudioLevel::multiplier_to_preview
936 (range.absmean() * gain, m);
937 meanTop = AudioLevel::multiplier_to_preview
938 (range.absmean() * gain, m);
924 } 939 }
925 break; 940 break;
926 941 }
927 case MeterScale: 942
928 if (!mergingChannels) { 943 rangeBottom = my - rangeBottom;
929 int r0 = abs(AudioLevel::multiplier_to_preview(range.min() * gain, m)); 944 rangeTop = my - rangeTop;
930 int r1 = abs(AudioLevel::multiplier_to_preview(range.max() * gain, m)); 945 meanBottom = my - meanBottom;
931 rangeTop = std::max(r0, r1); 946 meanTop = my - meanTop;
932 meanTop = std::min(r0, r1);
933 if (mixingChannels) rangeBottom = meanTop;
934 else rangeBottom = AudioLevel::multiplier_to_preview(range.absmean() * gain, m);
935 meanBottom = rangeBottom;
936 } else {
937 rangeBottom = -AudioLevel::multiplier_to_preview(range.min() * gain, m * greyLevels);
938 rangeTop = AudioLevel::multiplier_to_preview(range.max() * gain, m * greyLevels);
939 meanBottom = -AudioLevel::multiplier_to_preview(range.absmean() * gain, m);
940 meanTop = AudioLevel::multiplier_to_preview(range.absmean() * gain, m);
941 }
942 break;
943 }
944
945 rangeBottom = my * greyLevels - rangeBottom;
946 rangeTop = my * greyLevels - rangeTop;
947 meanBottom = my - meanBottom;
948 meanTop = my - meanTop;
949
950 int topFill = (rangeTop % greyLevels);
951 if (topFill > 0) topFill = greyLevels - topFill;
952
953 int bottomFill = (rangeBottom % greyLevels);
954
955 rangeTop = rangeTop / greyLevels;
956 rangeBottom = rangeBottom / greyLevels;
957 947
958 bool clipped = false; 948 bool clipped = false;
959 949
960 if (rangeTop < my - m) { rangeTop = my - m; } 950 if (rangeTop < my - m) { rangeTop = my - m; }
961 if (rangeTop > my + m) { rangeTop = my + m; } 951 if (rangeTop > my + m) { rangeTop = my + m; }
962 if (rangeBottom < my - m) { rangeBottom = my - m; } 952 if (rangeBottom < my - m) { rangeBottom = my - m; }
963 if (rangeBottom > my + m) { rangeBottom = my + m; } 953 if (rangeBottom > my + m) { rangeBottom = my + m; }
964 954
965 if (range.max() <= -1.0 || 955 if (range.max() <= -1.0 || range.max() >= 1.0) {
966 range.max() >= 1.0) clipped = true; 956 clipped = true;
957 }
967 958
968 if (meanBottom > rangeBottom) meanBottom = rangeBottom;
969 if (meanTop < rangeTop) meanTop = rangeTop;
970
971 bool drawMean = m_showMeans; 959 bool drawMean = m_showMeans;
972 if (meanTop == rangeTop) { 960
973 if (meanTop < meanBottom) ++meanTop; 961 meanTop = meanTop - 0.5;
974 else drawMean = false; 962 meanBottom = meanBottom + 0.5;
975 }
976 if (meanBottom == rangeBottom && m_scale == LinearScale) {
977 if (meanBottom > meanTop) --meanBottom;
978 else drawMean = false;
979 }
980
981 if (showIndividualSample) {
982 paint->setPen(baseColour);
983 paint->drawRect(x-1, rangeTop-1, 2, 2);
984 if (rangeTop != rangeBottom) { // e.g. for "butterfly" merging mode
985 paint->drawRect(x-1, rangeBottom-1, 2, 2);
986 }
987 }
988 963
989 if (x != x0 && prevRangeBottom != -1) { 964 if (meanTop <= rangeTop + 1.0) {
990 if (prevRangeBottom > rangeBottom + 1 && 965 meanTop = rangeTop + 1.0;
991 prevRangeTop > rangeBottom + 1) { 966 }
992 // paint->setPen(midColour); 967 if (meanBottom >= rangeBottom - 1.0 && m_scale == LinearScale) {
993 paint->setPen(baseColour); 968 meanBottom = rangeBottom - 1.0;
994 paint->drawLine(x-1, prevRangeTop, x, rangeBottom + 1); 969 }
995 paint->setPen(prevRangeTopColour); 970 if (meanTop > meanBottom - 1.0) {
996 paint->drawPoint(x-1, prevRangeTop); 971 drawMean = false;
997 } else if (prevRangeBottom < rangeTop - 1 &&
998 prevRangeTop < rangeTop - 1) {
999 // paint->setPen(midColour);
1000 paint->setPen(baseColour);
1001 paint->drawLine(x-1, prevRangeBottom, x, rangeTop - 1);
1002 paint->setPen(prevRangeBottomColour);
1003 paint->drawPoint(x-1, prevRangeBottom);
1004 }
1005 }
1006
1007 if (m_model->isReady()) {
1008 if (clipped /*!!! ||
1009 range.min() * gain <= -1.0 ||
1010 range.max() * gain >= 1.0 */) {
1011 paint->setPen(Qt::red); //!!! getContrastingColour
1012 } else {
1013 paint->setPen(baseColour);
1014 }
1015 } else {
1016 paint->setPen(midColour);
1017 } 972 }
1018 973
1019 #ifdef DEBUG_WAVEFORM_PAINT_BY_PIXEL 974 #ifdef DEBUG_WAVEFORM_PAINT_BY_PIXEL
1020 SVCERR << "range " << rangeBottom << " -> " << rangeTop << ", means " << meanBottom << " -> " << meanTop << ", raw range " << range.min() << " -> " << range.max() << endl; 975 SVCERR << "range " << rangeBottom << " -> " << rangeTop << ", means " << meanBottom << " -> " << meanTop << ", raw range " << range.min() << " -> " << range.max() << endl;
1021 #endif 976 #endif
1022 977
1023 if (rangeTop == rangeBottom) { 978 double rangeMiddle = (rangeTop + rangeBottom) / 2.0;
1024 paint->drawPoint(x, rangeTop); 979 bool trivialRange = (fabs(rangeTop - rangeBottom) < 1.0);
980 double px = x + 0.5;
981
982 if (showIndividualSample) {
983 individualSamplePoints.push_back(QPointF(px, rangeTop));
984 if (!trivialRange) {
985 // common e.g. in "butterfly" merging mode
986 individualSamplePoints.push_back(QPointF(px, rangeBottom));
987 }
988 }
989
990 if (firstPoint) {
991 waveformPath = QPainterPath(QPointF(px, rangeMiddle));
992 firstPoint = false;
1025 } else { 993 } else {
1026 paint->drawLine(x, rangeBottom, x, rangeTop); 994 waveformPath.lineTo(QPointF(px, rangeMiddle));
1027 } 995 }
1028 996
1029 prevRangeTopColour = baseColour; 997 if (!trivialRange) {
1030 prevRangeBottomColour = baseColour; 998 waveformPath.lineTo(QPointF(px, rangeTop));
1031 999 waveformPath.lineTo(QPointF(px, rangeBottom));
1032 if (m_greyscale && (m_scale == LinearScale) && m_model->isReady()) { 1000 waveformPath.lineTo(QPointF(px, rangeMiddle));
1033 if (!clipped) { 1001 }
1034 if (rangeTop < rangeBottom) { 1002
1035 if (topFill > 0 &&
1036 (!drawMean || (rangeTop < meanTop - 1))) {
1037 paint->setPen(greys[topFill - 1]);
1038 paint->drawPoint(x, rangeTop);
1039 prevRangeTopColour = greys[topFill - 1];
1040 }
1041 if (bottomFill > 0 &&
1042 (!drawMean || (rangeBottom > meanBottom + 1))) {
1043 paint->setPen(greys[bottomFill - 1]);
1044 paint->drawPoint(x, rangeBottom);
1045 prevRangeBottomColour = greys[bottomFill - 1];
1046 }
1047 }
1048 }
1049 }
1050
1051 if (drawMean) { 1003 if (drawMean) {
1052 paint->setPen(midColour); 1004 meanPath.moveTo(QPointF(px, meanBottom));
1053 paint->drawLine(x, meanBottom, x, meanTop); 1005 meanPath.lineTo(QPointF(px, meanTop));
1054 } 1006 }
1055 1007
1056 prevRangeBottom = rangeBottom; 1008 if (clipped) {
1057 prevRangeTop = rangeTop; 1009 if (trivialRange) {
1010 clipPath.moveTo(QPointF(px, rangeMiddle));
1011 clipPath.lineTo(QPointF(px+1, rangeMiddle));
1012 } else {
1013 clipPath.moveTo(QPointF(px, rangeBottom));
1014 clipPath.lineTo(QPointF(px, rangeTop));
1015 }
1016 }
1017 }
1018
1019 double penWidth = 1.0;
1020
1021 if (m_model->isReady()) {
1022 paint->setPen(QPen(baseColour, penWidth));
1023 } else {
1024 paint->setPen(QPen(midColour, penWidth));
1025 }
1026 paint->drawPath(waveformPath);
1027
1028 if (!clipPath.isEmpty()) {
1029 paint->save();
1030 paint->setPen(QPen(ColourDatabase::getInstance()->
1031 getContrastingColour(m_colour), penWidth));
1032 paint->drawPath(clipPath);
1033 paint->restore();
1034 }
1035
1036 if (!meanPath.isEmpty()) {
1037 paint->save();
1038 paint->setPen(QPen(midColour, penWidth));
1039 paint->drawPath(meanPath);
1040 paint->restore();
1041 }
1042
1043 if (!individualSamplePoints.empty()) {
1044 paint->save();
1045 paint->setPen(QPen(baseColour, penWidth));
1046 double sz = ViewManager::scalePixelSize(2.0);
1047 for (QPointF p: individualSamplePoints) {
1048 paint->drawRect(QRectF(p.x() - sz/2, p.y() - sz/2, sz, sz));
1049 }
1050 paint->restore();
1058 } 1051 }
1059 } 1052 }
1060 1053
1061 void 1054 void
1062 WaveformLayer::paintChannelScaleGuides(LayerGeometryProvider *v, 1055 WaveformLayer::paintChannelScaleGuides(LayerGeometryProvider *v,
1237 case MeterScale: 1230 case MeterScale:
1238 vy = AudioLevel::multiplier_to_preview(value, m); 1231 vy = AudioLevel::multiplier_to_preview(value, m);
1239 break; 1232 break;
1240 1233
1241 case dBScale: 1234 case dBScale:
1242 vy = dBscale(value, m); 1235 vy = int(dBscale(value, m));
1243 break; 1236 break;
1244 } 1237 }
1245 1238
1246 // SVCERR << "mergingChannels= " << mergingChannels << ", channel = " << channel << ", value = " << value << ", vy = " << vy << endl; 1239 // SVCERR << "mergingChannels= " << mergingChannels << ", channel = " << channel << ", value = " << value << ", vy = " << vy << endl;
1247 1240
1509 "middleLineHeight=\"%7\" " 1502 "middleLineHeight=\"%7\" "
1510 "aggressive=\"%8\" " 1503 "aggressive=\"%8\" "
1511 "autoNormalize=\"%9\"") 1504 "autoNormalize=\"%9\"")
1512 .arg(m_gain) 1505 .arg(m_gain)
1513 .arg(m_showMeans) 1506 .arg(m_showMeans)
1514 .arg(m_greyscale) 1507 .arg(true) // Option removed, but effectively always on, so
1508 // retained in the session file for compatibility
1515 .arg(m_channelMode) 1509 .arg(m_channelMode)
1516 .arg(m_channel) 1510 .arg(m_channel)
1517 .arg(m_scale) 1511 .arg(m_scale)
1518 .arg(m_middleLineHeight) 1512 .arg(m_middleLineHeight)
1519 .arg(m_aggressive) 1513 .arg(m_aggressive)
1534 1528
1535 bool showMeans = (attributes.value("showMeans") == "1" || 1529 bool showMeans = (attributes.value("showMeans") == "1" ||
1536 attributes.value("showMeans") == "true"); 1530 attributes.value("showMeans") == "true");
1537 setShowMeans(showMeans); 1531 setShowMeans(showMeans);
1538 1532
1539 bool greyscale = (attributes.value("greyscale") == "1" ||
1540 attributes.value("greyscale") == "true");
1541 setUseGreyscale(greyscale);
1542
1543 ChannelMode channelMode = (ChannelMode) 1533 ChannelMode channelMode = (ChannelMode)
1544 attributes.value("channelMode").toInt(&ok); 1534 attributes.value("channelMode").toInt(&ok);
1545 if (ok) setChannelMode(channelMode); 1535 if (ok) setChannelMode(channelMode);
1546 1536
1547 int channel = attributes.value("channel").toInt(&ok); 1537 int channel = attributes.value("channel").toInt(&ok);
1553 float middleLineHeight = attributes.value("middleLineHeight").toFloat(&ok); 1543 float middleLineHeight = attributes.value("middleLineHeight").toFloat(&ok);
1554 if (ok) setMiddleLineHeight(middleLineHeight); 1544 if (ok) setMiddleLineHeight(middleLineHeight);
1555 1545
1556 bool aggressive = (attributes.value("aggressive") == "1" || 1546 bool aggressive = (attributes.value("aggressive") == "1" ||
1557 attributes.value("aggressive") == "true"); 1547 attributes.value("aggressive") == "true");
1558 setUseGreyscale(aggressive); 1548 setAggressiveCacheing(aggressive);
1559 1549
1560 bool autoNormalize = (attributes.value("autoNormalize") == "1" || 1550 bool autoNormalize = (attributes.value("autoNormalize") == "1" ||
1561 attributes.value("autoNormalize") == "true"); 1551 attributes.value("autoNormalize") == "true");
1562 setAutoNormalize(autoNormalize); 1552 setAutoNormalize(autoNormalize);
1563 } 1553 }